Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GCC: template constructor instantiated when copy-constructor needed

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?

like image 241
DaviD. Avatar asked May 19 '14 15:05

DaviD.


People also ask

How copy constructor can be declared in what situations use of copy constructor is necessary?

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.

Is copy constructor created automatically?

No copy constructor is automatically generated.

When should a copy constructor be declared?

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 what way a copy constructor is automatically invoked?

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.


1 Answers

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!

like image 128
ecatmur Avatar answered Sep 20 '22 06:09

ecatmur