GCC: template constructor instantiated when copy-constructor needed - c++

In the following example, GCC >= 4.7 instantiates the template constructor (which you can observe by reading the error messages) although only the implicitly generated copy-constructor should be needed.
#include <type_traits>
// 'ambiguous' is ambiguous for 'ambiguous<int, int>'
template<typename A, typename B>
struct ambiguous : std::false_type {};
template<typename T>
struct ambiguous<int, T> : std::true_type {};
template<typename T>
struct ambiguous<T, int> : std::true_type {};
// quantity
template<typename Type>
class quantity
{
public:
quantity() = default;
// Copy-constructor is implicitly created
// Template constructor
template<
typename T,
typename = typename std::enable_if<ambiguous<Type, T>::value>::type
>
quantity(quantity<T>) {}
template<
typename T,
typename = typename std::enable_if<ambiguous<Type, T>::value>::type
>
void set(quantity<T>) {}
};
// main
int main()
{
quantity<int> a;
quantity<float> b;
b.set(a);
}
The above code compiles in GCC < 4.7, clang and MSVS (don't know which version, I used the one from http://rextester.com/runcode). In GCC >= 4.7 compilation fails with the following message:
main.cpp: In substitution of ‘template<class T, class> quantity<Type>::quantity(quantity<T>) [with T = int; <template-parameter-1-2> = <missing>]’:
main.cpp:39:12: required from here
main.cpp:23:9: error: ambiguous class template instantiation for ‘struct ambiguous<int, int>’
typename = typename std::enable_if<ambiguous<Type, T>::value>::type
^
main.cpp:9:8: error: candidates are: struct ambiguous<int, T>
struct ambiguous<int, T> : std::true_type {};
^
main.cpp:12:8: error: struct ambiguous<T, int>
struct ambiguous<T, int> : std::true_type {};
^
main.cpp: In function ‘int main()’:
main.cpp:31:10: error: initializing argument 1 of ‘void quantity<Type>::set(quantity<T>) [with T = int; <template-parameter-2-2> = void; Type = float]’
void set(quantity<T>) {}
So when invoking b.set(a);, GCC apparently looks for a copy constructor and on the way instantiates the template constructor which in turn instantiates ambiguous<int, int> which is (uhm...) ambiguous.
Question: Is GCC right to instantiate the template constructor even though a copy constructor is needed?

gcc is correct.
There are a couple of issues here which unfortunately have become conflated in your question:
First, the behavior of gcc < 4.7 is not fundamentally different; all versions of gcc since (at least) 4.4 reject the very similar program:
struct S;
template<typename, typename> struct U {};
template<typename T> struct U<S, T> {};
template<typename T> struct U<T, S> {};
struct S {
S() = default;
template<typename T, typename = typename U<S, T>::type> S(T) {}
};
int main() {
S a;
S b(a);
}
Note that the only real difference is that the copy-initialization is explicit rather than contained in a function call. Clang accepts this program, by the way.
Next, it's not fundamental to this issue that the copy constructor be involved (rule 12.8p6 in C++11); here's another similar program that gcc (all versions) rejects and clang accepts:
struct S {};
template<typename, typename> struct U {};
template<typename T> struct U<S, T> {};
template<typename T> struct U<T, S> {};
void f(S);
template<typename T> typename U<S, T>::type f(T);
int main() {
S a;
f(a);
}
The difference between clang and gcc is in the application of 14.8.2p8:
[...] [ Note: The evaluation of the substituted types and expressions can result in side effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such side effects are not in the "immediate context" and can result in the program being ill-formed. — end note ]
The ambiguity in the template specialization ambiguous<int, int> is outside the immediate context, so the program is ill-formed. (A supporting argument for this is that template specialization ambiguity does not appear in the succeeding list of reasons for type deduction to fail).
MSVC is different again; it accepts the following program that both clang and gcc reject:
template<typename T> struct U { typedef typename T::type type; };
struct S {
S() = default;
template<typename T, typename = typename U<T>::type> S(T) {}
};
int main() {
S a;
S b(a);
}
This is down to rule 12.8p6:
A declaration of a constructor for a class X is ill-formed if its first parameter is of type (optionally cv-qualified) X and either there are no other parameters or else all other parameters have default arguments. A member function template is never instantiated to produce such a constructor signature.
However, in order to determine whether a member function template instantiation is a constructor ill-formed with regard to 12.8p6, it is necessary to instantiate its declaration (cf. 14.7.1p9). Note that MSVC rejects the following program, so it isn't even consistent:
template<typename T> struct U { typedef typename T::type type; };
struct S {
S() = default;
template<typename T> S(T, typename U<T>::type *p = 0) {}
};
int main() {
S a;
S b(a);
}
This has some highly amusing behavior effects; MSVC accepts the following (ill-formed) program:
template<typename T> struct U { typedef typename T::type type; };
struct S {
S() = default;
template<typename T, typename = typename U<T>::type> S(T) {}
};
template<typename T> typename U<T>::type f(T) { return 0; }
int main() {
S a;
S b(a); // XXX
f(a);
}
However if the copy-initialization S b(a) is commented out, the program is rejected!

Related

Compiler discrepancy concerning type-deduction

I'm trying to construct an object from a templated class which should be of iterator-type based on the container that is being passed through its constructor. To do so, I use a deduction guide which extracts the iterator member-type.
This seems to work on both Clang and GCC. The problem is that when making some seemingly unrelated changes to this class, the code no longer compiles on Clang (but does on some versions of GCC). Here is the code, which I unfortunately couldn't make much smaller:
#include <array>
#include <iterator>
#include <type_traits>
template<typename It>
using category_t = typename std::iterator_traits<It>::iterator_category;
//
template<typename Iter>
struct is_random_access_iter
: std::is_convertible<
category_t<Iter>,
std::random_access_iterator_tag> {};
template<typename T>
struct has_random_access_iter
: is_random_access_iter<typename T::iterator> {};
template<typename T>
struct is_unary_type
: std::conjunction<
std::is_default_constructible<T>,
std::is_copy_constructible<T>> {};
//
template<typename Iter>
inline constexpr auto is_random_access_iter_v
= bool{is_random_access_iter<Iter>{}};
template<typename T>
inline constexpr auto has_random_access_iter_v
= bool{has_random_access_iter<T>{}};
template<typename T>
inline constexpr auto is_unary_type_v
= bool{is_unary_type<T>{}};
//
template<typename It, int Size,
typename = std::enable_if_t<is_random_access_iter<It>{}>>
struct derp {
// constexpr derp() = default;
template<typename T,
typename = std::enable_if_t<is_unary_type<T>{}>,
typename = std::enable_if_t<has_random_access_iter_v<T>>>
// typename = std::enable_if_t<is_random_access_iter_v<typename T::iterator>>>
constexpr explicit derp(T& herp)
: iter{herp.begin()} {}
It iter;
};
template<typename T>
derp(T) -> derp<typename T::iterator, std::tuple_size<T>{}>;
template<typename T>
struct wrap {
constexpr wrap() = default;
constexpr explicit wrap(T& herp)
: herp{herp} {}
derp<typename T::iterator, std::tuple_size<T>{}> herp;
};
auto main() -> int {
auto arr = std::array<int, 3>{};
wrap{arr};
}
One of the problems comes to light when uncommenting the default constructor of derp. It seems as if derp is being instantiated by its own type, instead of the iterator-type from the passed-in container:
GCC ARM 7.2.1:
<source>:44:9: required by substitution of 'template<class T, class, class> constexpr derp<int*, 3, void>::derp(T&) [with T = const derp<int*, 3, void>; <template-parameter-1-2> = void; <template-parameter-1-3> = <missing>]'
<source>:66:13: required from here
<source>:16:8: error: no type named 'iterator' in 'const struct derp<int*, 3, void>'
Clang:
<source>:17:41: error: no type named 'iterator' in 'derp<int *, 3, void>'
: is_random_access_iter<typename T::iterator> {};
~~~~~~~~~~~~^~~~~~~~
<source>:44:37: note: in instantiation of template class 'has_random_access_iter<const derp<int *, 3, void>>' requested here
typename = std::enable_if_t<has_random_access_iter<T>{}>>
^
<source>:46:24: note: in instantiation of default argument for 'derp<const derp<int *, 3, void>, void>' required here
constexpr explicit derp(T& herp)
The code no longer compiles on Clang and GCC ARM 7.2.1. It does however compile on GCC trunk. Why does enabling the default constructor cause a compilation error? And why does GCC >= 11.1 seems to be unaffected?
Live example

SFINAE type trait with pointer-to-member-function fails

I'm practicing SFINAE and would like to implement a type trait in order to check if a given class T contains a method print(). I have the following two variants:
// (1)
template <typename T, typename = int>
struct has_print_method : std::false_type {};
template <typename T>
struct has_print_method<T, decltype(&T::print, 0)> : std::true_type {};
template <typename T>
const bool has_print_method_v = has_print_method<T>::value;
// (2)
template <typename T>
struct has_print_method{
template <typename U, typename = void> struct helper : std::false_type{};
template <typename U> struct helper<U, decltype(&U::print)> : std::true_type{};
static const bool value = helper<T, void (T::*)() const>::value;
};
template <typename T>
const bool has_print_method_v = has_print_method<T>::value;
(1) only checks for the existence of a member method print() and ignores the member method's signature. Whereas (2) checks the method's signature, i.e. it requires a member method void print() const.
I test both variants via:
#include <iostream>
#include <type_traits>
// Simple Class A
struct A{
int a;
public:
void print() const{}
};
// (1) or (2) here
template<typename T, std::enable_if_t<has_print_method_v<T>, bool> = true>
void print(T t) {
t.print();
}
void print(double x){
std::cout << x << '\n';
}
int main() {
A a;
print(a);
print(1.0); // (*)
return 0;
}
Using the type trait (1) and compiling with clang 12.0 and std=c++17 flag works as expected. However, using (2) instead, I obtain
<source>:28:50: error: member pointer refers into non-class type 'double'
static const bool value = helper<T, void (T::*)() const>::value;
^
<source>:32:33: note: in instantiation of template class 'has_print_method<double>' requested here
const bool has_print_method_v = has_print_method<T>::value;
^
<source>:34:39: note: in instantiation of variable template specialization 'has_print_method_v<double>' requested here
template<typename T, std::enable_if_t<has_print_method_v<T>, bool> = true>
^
<source>:35:6: note: while substituting prior template arguments into non-type template parameter [with T = double]
void print(T t) {
^~~~~~~~~~~~
<source>:47:5: note: while substituting deduced template arguments into function template 'print' [with T = double, $1 = (no value)]
print(1.0);
^
1 error generated.
What am I missing here? You can find the example here on godbolt. Edit: Um, I have just noticed that both versions compile without an error with gcc11.1. Strange.
First up, judging by your error messages and my own tests, I think you mean that type trait (1) works and (2) doesn't. On that basis, here is a version of trait (1) that tests for a matching function signature:
template <typename T, typename = int>
struct has_print_method : std::false_type {};
template <typename T>
struct has_print_method<T, decltype(&T::print, 0)> : std::is_same <decltype(&T::print), void (T::*)() const> {};
template <typename T>
const bool has_print_method_v = has_print_method<T>::value;
Live demo

SFINAE on Error in Dependent Type causes unexpected hard error

I have code that I can simplify down to something like this:
#include <type_traits>
template <typename T>
struct dependent
{
using type = typename T::type;
};
template <typename T>
typename dependent<T>::type
foo(const T& x);
bool foo(bool x) { return x; }
int main()
{
foo(true);
}
This fails to compile with g++ 9.3 with --std=c++17 with the error:
test.cpp: In instantiation of 'struct dependent<bool>':
test.cpp:11:1: required by substitution of 'template<class T> typename dependent<T>::type foo(const T&) [with T = bool]'
test.cpp:17:13: required from here
test.cpp:6:11: error: 'bool' is not a class, struct, or union type
6 | using type = typename T::type;
| ^~~~
This is not what I would expect. I would expect that attempting to substitute bool for T in template <typename T> typename dependent<T>::type foo(const T& x) would be a failure, which is not an error. It seems SFINAE is not working for me, but I do not know why.
From the examples in the unofficial reference on SFINAE:
Substitution proceeds in lexical order and stops when a failure is encountered.
template <typename A>
struct B { using type = typename A::type; };
template <
class T,
class = typename T::type, // SFINAE failure if T has no member type
class U = typename B<T>::type // hard error if T has no member type
// (guaranteed to not occur as of C++14)
> void foo (int);
I am hitting the case on class U = typename B<T>::type, but the "guaranteed to not occur as of C++14" bit seems to indicate that this should not be happening as of C++14. What gives?
Issue is that dependent<T> has type, but that one might be ill formed causing hard failure.
You might make dependent SFINAE friendly:
template <typename T, typename Enabler = void>
struct dependent
{
};
template <typename T>
struct dependent<T, std::void_t<typename T::type>>
{
using type = typename T::type;
};
Demo

gcc vs. clang: "invalid use of incomplete type" with std::declval and template specialization

I have a method fun which is contained within a struct Impl for partial specialization. The check is_derived_from_template is used to find out whether a generic Impl::fun can be used for a type if it derives from a specific template. Otherwise, Impl is partially specialized explicitly.
#include <iostream>
template <typename T, typename U>
struct Base{};
// Forward declaration
struct Foo;
struct Bar;
template <template<typename...> class T, typename U>
struct is_derived_from_template
{
private:
template<typename... Args>
static decltype(static_cast<const T<Args...>&>(std::declval<U>()), std::true_type{}) test(const T<Args...>&);
static std::false_type test(...);
public:
static constexpr bool value = decltype(test(std::declval<U>()))::value;
};
template <typename T, typename = void>
struct Impl
{
static void fun(T& x);
};
template <typename T>
struct Impl<T, typename std::enable_if<is_derived_from_template<Base, T>::value>::type>
{
static void fun(T& base)
{
std::cout << "Base" << std::endl;
}
};
template <>
void Impl<Foo>::fun(Foo& t)
{
std::cout << "Foo" << std::endl;
}
struct Foo {};
struct Bar : Base<int,double> {};
int main()
{
Foo foo;
Bar bar;
Impl<Foo>::fun(foo);
Impl<Bar>::fun(bar);
}
When compiling this code with gcc, I get the following error:
main.cpp: In instantiation of 'constexpr const bool is_derived_from_template<std::vector, Foo>::value':
main.cpp:33:15: required from here
main.cpp:15:48: error: invalid use of incomplete type 'struct Foo'
static constexpr bool value = decltype(test(std::declval<U>()))::value;
^
main.cpp:5:8: note: forward declaration of 'struct Foo'
struct Foo;
^
gcc live demo
However, clang compiles this without an error and the output is as expected:
Foo
Base
clang live demo
Which of the two compilers is right?
How can I modify my code to get it to work with gcc?
Reduced to
#include <utility>
void f(...);
class C;
using type = decltype(f(std::declval<C>()));
Compiles on Clang, errors on GCC.
I'm inclined to say that GCC is right here, because passing an object of class type via ... requires copying, and you can't copy something with an incomplete type.
If you want to, you can use pointers instead in your SFINAE:
template <template<typename...> class T, typename U>
struct is_derived_from_template
{
private:
template<typename... Args>
static decltype(static_cast<const T<Args...>&>(std::declval<U>()), std::true_type{}) test(const T<Args...>*);
static std::false_type test(...);
public:
static constexpr bool value = decltype(test(std::declval<U*>()))::value;
};
Although you should be cautious about allowing is_derived_from_template to be instantiated with an incomplete type, since it can easily lead to ODR violations if the complete type turns out to be derived from the specified template.
1.Clang compiles a bit differently than traditional compilers like GCC. GCC would be right in the sense that it parses code 'traditionally' compared to Clang, and that you should define your types before using them.
You can find a comparison here.
2.Changing:
// Forward declaration
struct Foo;
struct Bar;
to:
struct Foo {};
struct Bar : Base<int,double> {};
Worked for me.

SFINAE enable_if explicit constructor

I'm trying to switch between an explicit and an implicit conversion constructor via enable_if.
My code currently looks like
#include <type_traits>
#include <cstdint>
enum class enabled {};
template <bool B, typename T = void> using enable_if_t = typename std::enable_if<B, T>::type;
template <bool B, typename T = void> using disable_if_t = typename std::enable_if<!B, T>::type;
template <std::intmax_t A> struct SStruct
{
static constexpr std::intmax_t a = A;
};
template <typename T> struct SCheckEnable : std::integral_constant<bool, T::a == 0>
{
};
template <typename U, typename T> class CClass
{
public:
template <typename T2, enable_if_t<SCheckEnable<U>::value, enabled>...> constexpr CClass(T2 v) : val(v) {};
template <typename T2, disable_if_t<SCheckEnable<U>::value, enabled>...> explicit constexpr CClass(T2 v) : val(v) {};
private:
T val;
};
int main()
{
CClass<SStruct<0>, double> a = 1; // should use implicit constructor
CClass<SStruct<1>, double> b = CClass<SStruct<1>, double>(1); // should use explicit constructor
}
The true in the enable_ifs is dependent of the template parameter U
If I try to compile this minimal example with g++ 4.9.1 and --std=c++11 enabled I get the following errors
sfinae.cpp: In substitution of ‘template<bool B, class T> using disable_if_t = typename std::enable_if<(! B), T>::type [with bool B = true; T = enabled]’:
sfinae.cpp:13:52: required from here
sfinae.cpp:7:95: error: no type named ‘type’ in ‘struct std::enable_if<false, enabled>’
template <bool B, typename T = void> using disable_if_t = typename std::enable_if<!B, T>::type;
^
sfinae.cpp:19:68: error: prototype for ‘constexpr CClass<U, T>::CClass(T2)’ does not match any in class ‘CClass<U, T>’
template <typename U, typename T> template <typename T2> constexpr CClass<U, T>::CClass(T2 v) : val(v)
^
sfinae.cpp:13:77: error: candidates are: template<class U, class T> template<class T2, int ...<anonymous> > constexpr CClass<U, T>::CClass(T2)
template <typename T2, disable_if_t<true, enabled>...> explicit constexpr CClass(T2 v);
^
sfinae.cpp:12:67: error: template<class U, class T> template<class T2, enabled ...<anonymous> > constexpr CClass<U, T>::CClass(T2)
template <typename T2, enable_if_t<true, enabled>...> constexpr CClass(T2 v);
^
Any idea how to select between explicit and implicit construction based on parameter U here?
Use
template <class...> struct null_v : std::integral_constant<int, 0> {};
and define the constructors as
template <typename T2,
long = null_v<enable_if_t<SCheckEnable<U>::value, T2>>::value>
constexpr CClass(T2 v) : val(v) {};
template <typename T2,
int = null_v<disable_if_t<SCheckEnable<U>::value, T2>>::value>
explicit constexpr CClass(T2 v) : val(v) {};
Making the argument dependent and actually instantiated.
Demo.
[temp.deduct]/8:
If a substitution results in an invalid type or expression, type
deduction fails. An invalid type or expression is one that would be
ill-formed if written using the substituted arguments.
In your case the error occurs outside of any substitution, so that's not causing a deduction failure but rather makes your code ill-formed.