Why doesn't the standard consider a template constructor as a copy constructor?
Asked Answered
C

3

33

Here's the definition of copy constructor, [class.copy.ctor/1]:

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 ([dcl.fct.default]).

Why does the standard exclude templates as copy constructors?

In this simple example, both constructors are copy constructors:

struct Foo {
    Foo(const Foo &); // copy constructor
    Foo(Foo &);       // copy constructor
};

See this similar example:

struct Foo {
     Foo() = default;

     template <typename T>
     Foo(T &) {
         printf("here\n");
     }
};

int main() {
    Foo a;
    Foo b = a;
}

In this example, here will be printed. So it seems that my template constructor is a copy constructor, at least it behaves like one (it is called in a context where copy constructors are usually called).

Why is the "non-template" requirement there in the text?

Catron answered 25/4, 2019 at 9:22 Comment(9)
Note: I'm note sure Foo b = a instantiates and calls Foo::Foo<Foo>(Foo&). It might rather call the implicitly declared copy constructor.Archfiend
@Archfiend Both gcc and clang (tested on Coliru) actually do print "here" from this code.Branscum
@Angew maybe they are wrong then, in regard of [class.copy.ctor]/1 & /6.Archfiend
What happens if you Foo c = std::move(a); ?Absinthe
see https://mcmap.net/q/453354/-template-quot-copy-constructor-quot-does-not-prevent-compiler-generated-move-constructorCharpentier
@Absinthe nice test: coliru.stacked-crooked.com/a/cff8c0b034585446 nothing is printed (gcc trunk)Archfiend
A ”copy constructor” has a special status. It’s not just any old constructor that can be used for copying.Deterrent
Technically, this quoted paragraph of the standard does not forbid template copy c'tors. It only says under which circumstances a non-template c'tor is considered a copy c'tor. It doesn't say anything at all about template c'tors. In fact, I can't find a section saying "template c'tros can never be copy c'tors". eel.is/c++draft/class.copy.ctor#5 comes close, but only handles value type argument which itself would need a copy c'tor to be passed. OP's case template <typename T> Foo(T&) isn't handled anywhere. Am I missing something?Pavlov
@sebrockm: the standard specifies what a copy constructor is. It defines, that if a constructor is non-template, and has specific parameters, then it is a copy-constructor. All the other constructors are not copy-constructors.Catron
D
32

Let's put templates aside for a second. If a class doesn't declare a copy constructor, an implicitly defaulted one is generated. It may be defined as deleted, but it's defaulted nonetheless.

A member template is not a member function. Members are instantiated from it only when needed.

So how can a compiler know from the class definition alone whether or not a specialization with T = Foo will ever be needed? It can't. But it's exactly that on which it needs to base a decision of how to handle a potential need for an implicitly defaulted copy constructor (AND move constructor). That becomes messy.

The easiest approach is to exclude templates. We'll always have some copy constructor anyway, it will do the correct thingTM by default, and will be favored by overload resolution because it's not instantiated from a template.

Daina answered 25/4, 2019 at 9:40 Comment(7)
What's your opinion on g++ & clang++ printing "here" in the program shown in the Q then?Archfiend
So basically you say that this is because of the rule: "If the class definition does not explicitly declare a copy constructor, a non-explicit one is declared implicitly". And we still expect that the usual implicit copy constructor is defined, no matter of the templated constructor. Yep, this seems the explanation.Catron
@Archfiend - Honestly? Feels like a bug to me. I can't experiment more at the moment to comment more about it. This is from my phone.Daina
@Catron - Exactly!Daina
@YSC- Or maybe... the defaulted one has a const qualified parameter, while the template doesn't. So It's a better match. Does it still happen with T const &?Daina
@YSC: it is printed, because the parameter passed is not const, so the template is a better match than the implicit one. But it is confusing (for me) to not call the template specialization as copy constructor. But the standard does this because of StoryTeller's explanation. At least, this is a sensible explanationCatron
@StoryTeller nice catch, no it doesn't : coliru.stacked-crooked.com/a/1de13995fde629beArchfiend
H
10

Why is the "non-template" requirement there in the text?

Given it were different and copy constructors could be templates. How could a non-copy constructor not be ambiguous in the presence of a copy constructor template? Consider this:

struct Foo {
   // ctor template: clearly useful and necessary
   template <typename T>
      Foo(const T&) {}

   // copy ctor: same signature! can't work
   template <typename T>
      Foo(const T &) {}
};

Besides, constructing a Foo from an object that is not a Foo can be achieved by either conversion or ordinary construction, but allowing for copy-construction from a non-Foo object changes the notion of copying to copying including conversion. But this can already be implemented with the existing scheme (conversion or non-copy construction).

In this example, here will be printed. So it seems that my template constructor is a copy constructor

The example that you show doesn't invoke copy construction, but an ordinary, implicit construction. If you change the constructor template to

template <typename T>
Foo(const T &) {
//  ^^^^^
    printf("here\n");
}

then Foo b = a; results in the compiler-generated copy constructor being called. Note that the copy ctor generated by the compiler has this signature:

Foo(const Foo&);

This requires adding a const-qualifier to a in Foo b = a;. The original constructor template Foo(T&) in your snippet is a better match, as no const-qualifier is added.

Hydroxide answered 25/4, 2019 at 9:41 Comment(4)
Ah yes! Indeed the whole const vs non-const thing. +1Daina
I don't understand the first part of your answer. Did you intend to put template for both constructors? What I considered logical is that if a constructor is specialized with T=Foo, then it is a copy constructor.Catron
Both template were intended, I tried to make the point that if you have an ordinary ctor template (because it's useful), but you also need to implement copy constructor for your class (because it manages a resource), they can't co-exist, but they also can't be one entity, as they serve a different purpose. Admittedly, I see your point though, but it is realistic scenario that you have a general-purpose ctor template that does the right thing when instantiated with Foo? I you'd need to specialize it, this nothing but the non-template copy ctor, right?Hydroxide
@lubgr: Sorry, I meant "instantiated with T=Foo" (so not explicit specialization). I got confused with these terms lately after reading an answer here in SO. I think I need to read the standard about this (I remembered that what we call instantiation, the standard calls implicit(ly instantiated) specialization).Catron
W
0

A copy constructor is of the form X(X& ) or (X const&), and it will be provided for you by the compiler if you didn't declare one yourself. Non-template comes here probably due to issues if you use template classes.

Let's say there is a template class that has a template copy constructor. The problem is that when you instantiate that class using another instance of this class with the same template type, your template copy constructor will not be called.

The issue isn't that your copy constructor template doesn't match. The issue is that the implicit copy constructor is not a function template, and non-templates are preferred to template specializations when it comes to overload resolution.

Source: C++ template copy constructor on template class

Willyt answered 25/4, 2019 at 9:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.