6
template <typename T>
class A {
public:
    template<class C> A(const A<C>&) {}
    A(A&&) {}
};

void f(A<int>& a)
{
    A<int> b(a);
}

The above code doesn't compile in g++/clang++ reporting copy constructor is deleted because of user-provided move constructor (though vc++ compiles OK). Is there any standard requirement that prevents a template constructor from being selected during overload resolution (I know it's not a copy constructor though)? Or is there a requirement that when initializer has the same type as the initializee, it has to select a copy constructor?

goodbyeera
  • 3,169
  • 2
  • 18
  • 39

1 Answers1

4

Note; gcc and clang shows the correct behavior when trying to compile the provided snippet.


Why is vc++ wrong to accept the snippet?

  1. A function template can never be instantiated in a way that produces a copy-constructor, as can be read in the two following quotes from the c++ standard:

    [class.copy]

    2) A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X&, or const volatile X& and either there are no other parameters or else all other parameters have default arguments (8.3.6).

    ...

    6) A declaration of a constructor for 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.

  2. If a move-constructor is explicitly declared the implicitly generated copy-constructor won't be callable, see the following:

    [class.copy]

    7) If a class definition does not explicitly declare a copy constructor, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted (8.4)


struct Obj {
  template<typename T>
  Obj (T const&) { }
  Obj (Obj&&) { }
};

...

Obj a;
Obj b (a);

Having the previously mentioned quotes from the c++ standard in mind we can easily see that the above definition of Obj would be semantically equivalent of the below since our explicitly declared move-constructor will make our implicitly declared copy-constructor = delete;.

struct Obj {
  template<typename T>
  Obj (T const&) { }
  Obj (Obj const&) = delete;
  Obj (Obj&&) { }
};

According to the rules of overload resolution deleted functions still participate when trying to find the best match, but if they win the battle of having the best fit the program is ill-formed. This is exactly what is happening in the snippet.

Obj (Obj const&) = delete is a better fit than template<typename T> Obj (T const&), but since the copy-constructor isn't callable the snippet fails to compile.

Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
  • +1 Your answer's actually correct (and mine's (deleted) wrong). I didn't see that my quoted clause was actually pertaining to non-ref-qualified supposed copy or move constructor. /cc @juanchopanza – Mark Garcia Mar 04 '14 at 07:14
  • The questioner says he knows the template constructor is not a copy constructor. The real reason for the compile failure is that both the template constructor and the deleted copy constructor participate in the overload resolution and the deleted copy constructor is selected. – neverhoodboy Mar 04 '14 at 07:17
  • The template *could* result in the construction of an object in a way that looks like copy construction (imagine one involving a single implicit conversion, for example). The issue is that there is a copy constructor participating in overload resolution. – juanchopanza Mar 04 '14 at 07:19
  • @neverhoodboy what do you reckon about the updated answer? – Filip Roséen - refp Mar 04 '14 at 07:27
  • 1
    @FilipRoséen: I would say it's both correct and informational. Thanks for the effort. – neverhoodboy Mar 04 '14 at 07:37
  • Regarding "A declaration of a constructor for class X is ill-formed", that is for **pass by value**. The reason for the rule is that allowing a pass-by-value copy constructor would lead to infinite regress, using the copy constructor to create its own formal argument, and so on. This case does not apply for the OP's code. – Cheers and hth. - Alf Mar 04 '14 at 07:40
  • @Cheersandhth.-Alf it's intentionally left in to prevent follow-up questions of similar nature, if you feel like it's a cause of confusion please feel free to edit the post in question, thank you. – Filip Roséen - refp Mar 04 '14 at 07:43
  • @FilipRoséen: I would say that the wording "constructor template that is not a copy or move constructor" elsewhere in C++11 standard, indicates that a constructor template *can be* a copy constructor in some sense of the word, which in turn indicates an ambiguity that was not present in C++03 :(. Not sure what that is really about though. Anyway, the reason for non-instantiation is simply that if there is not a user-declared (non-template) copy constructor, then one is declared *implicitly*, and this always present declaration always wins over the template, *even if deleted*. – Cheers and hth. - Alf Mar 04 '14 at 07:57