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?
An explicit copy constructor is one that is declared explicit by using the explicit keyword. For example: explicit X(const X& copy_from_me); It is used to prevent copying of objects at function calls or with the copy-initialization syntax.
No copy constructor is automatically generated.
A copy constructor is called when a new object is created from an existing object, as a copy of the existing object. The assignment operator is called when an already initialized object is assigned a new value from another existing object.
In C++, a Copy Constructor may be called for the following cases: 1) When an object of the class is returned by value. 2) When an object of the class is passed (to a function) by value as an argument. 3) When an object is constructed based on another object of the same class.
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With