Setting aside edge cases we need not fritter away time on, the copy constructor for a class C takes a parameter of type const C&. If there is no such function declared in a class and such a function is needed (because it is used somewhere), it is automatically generated. This is absolutely true in C++98 and is typically true in C++11, and the difference between absolutely and typically is not of interest here. Where I'm going with this does not require acknowledging the nooks and crannies along the way.
In C++98, you could use a template to write what I called a "generalized copy constructor," and you did it like this:
class Widget { public: template<typename T> Widget(const T&); // ... };The key thing to recognize was that this template, though it could be instantiated to yield the signature of a copy constructor, is itself not a copy constructor, and that means that the class does not declare one. That being the case, if the copy constructor is needed, the compiler generates one as usual, because that's the rule: if there is no user-declared copy constructor (i.e., constructor taking a const Widget& parameter) and one is needed, the compiler generates one. The fact that such a function could be generated from a user-declared template is irrelevant.
When the compiler sees a request to copy an object, it still has to consider the possibility of calling a template instantiation, but the template instantiation has the same signature as the implicitly-generated copy constructor, and (1) the copy constructor is a function (not a template function) and (2) when a function and template function are equally good matches for a function call, overloading resolution chooses the function. The end result is that copy constructors can never be generated from templates.
To the best of my knowledge, all this remains true in C++11.
What changes in C++11 is the possibility of writing a generalized copy constructor taking a universal reference--a universal copy constructor:
class Widget { public: template<typename T> Widget(T&&); // now takes universal reference // ... };Consider now this code:
Widget w; Widget wcopy = w; // copy w to wcopyThis is a request to copy w. Widget has no copy constructor, so the compiler generates one with the default signature. The resulting class looks like this:
class Widget { public: Widget(const Widget&); // copy constructor (compiler-generated) template<typename T> Widget(T&&); // universal copy constructor // ... };When the compiler see the request to copy w, it knows it can call the copy constructor, just like it could in C++98, but it also knows that it can instantiate the template. Unlike the C++98 template, however, this one lacks a const-qualification on its parameter, so the resulting instantiation also lacks one. After instantiation, the class effectively looks like this:
class Widget { public: Widget(const Widget&); // copy constructor (compiler-generated) Widget(Widget&); // instantiated template // ... };Overload resolution now kicks in to choose from these two candidate functions. (Technically, one is a template function, not a function, and to be really technical about it, it's a template specialization, not a template instantiation, but none of that matters here.)
The parameter to these functions would be the object being copied. That's w. w is not const. A call to the compiler-generated copy constructor would require the addition of const to get an exact match, but the call to the instantiated template requires no such addition. As a result, it's a better match. The copy constructor will not be called to copy w. Instead, the instantiated template will.
The situation changes if we copy a const Widget:
const Widget cw; Widget cwcopy = cw; // copy a const WidgetNow we're back in C++98-land. The compiler-generated copy constructor and the template instantiation have the same signature (both take const Widget& parameters), so the function wins.
At least that's how I view it. gcc 4.7 agrees. Given this program,
#include <iostream> class Widget { public: Widget(){}; Widget(const Widget&) { std::cout << "Widget copy ctor "; } template<typename T> Widget(const T&) { std::cout << "Generalized Widget copy ctor "; } template<typename T> Widget(T&&) { std::cout << "Universal Widget ctor "; } }; void endLine() { std::cout << '\n'; } int main() { Widget w; { std::cout << "Create Widget from Widget:\n"; std::cout << " Direct init w/parens: "; Widget wcopy1(w); endLine(); std::cout << " Copy init : "; Widget wcopy2 = w; endLine(); std::cout << " Direct init w/braces: "; Widget wcopy3 {w}; endLine(); endLine(); } const Widget cw; { std::cout << "Create Widget from const Widget:\n"; std::cout << " Direct init w/parens: "; Widget wcopy1(cw); endLine(); std::cout << " Copy init : "; Widget wcopy2 = cw; endLine(); std::cout << " Direct init w/braces: "; Widget wcopy3 {cw}; endLine(); endLine(); } }gcc 4.7 produces this output:
Create Widget from Widget: Direct init w/parens: Universal Widget ctor Copy init : Universal Widget ctor Direct init w/braces: Universal Widget ctor Create Widget from const Widget: Direct init w/parens: Widget copy ctor Copy init : Widget copy ctor Direct init w/braces: Widget copy ctorNothing surprising there, but if I use auto to deduce the type of the copy, gcc's output changes. That is, this source code,
int main() { Widget w; { std::cout << "Create auto from Widget:\n"; std::cout << " Direct init w/parens: "; auto wcopy1(w); endLine(); // now using auto std::cout << " Copy init : "; auto wcopy2 = w; endLine(); std::cout << " Direct init w/braces: "; auto wcopy3 {w}; endLine(); endLine(); } const Widget cw; { std::cout << "Create auto from const Widget:\n"; std::cout << " Direct init w/parens: "; auto wcopy1(cw); endLine(); std::cout << " Copy init : "; auto wcopy2 = cw; endLine(); std::cout << " Direct init w/braces: "; auto wcopy3 {cw}; endLine(); endLine(); } }produces this output:
Create auto from Widget: Direct init w/parens: Universal Widget ctor Copy init : Universal Widget ctor Direct init w/braces: Universal Widget ctor Universal Widget ctor Create auto from const Widget: Direct init w/parens: Widget copy ctor Copy init : Widget copy ctor Direct init w/braces: Widget copy ctor Universal Widget ctorThis is surprising. Where are those extra constructor calls coming from? (In a comment on my last post, Julain posted results for clang, which did not show such extra calls.)
I had a theory. To test it, I added a move constructor to Widget,
class Widget { public: Widget(){}; Widget(const Widget&) { std::cout << "Widget copy ctor "; } Widget(Widget&&) { std::cout << "Widget move ctor "; } // added this template<typename T> Widget(const T&) { std::cout << "Generalized Widget copy ctor "; } template<typename T> Widget(T&&) { std::cout << "Universal Widget ctor "; } };then reran the test (using auto-declared variables). The revised results?
Create auto from Widget: Direct init w/parens: Universal Widget ctor Copy init : Universal Widget ctor Direct init w/braces: Universal Widget ctor Create auto from const Widget: Direct init w/parens: Widget copy ctor Copy init : Widget copy ctor Direct init w/braces: Widget copy ctorNo extra constructor calls. Funky, no?
Some of the following analysis is incorrect, but rather than simply edit the post to remove the wrong information and replace it with correct information, I'll add comments like this.gcc is making an extra call to the universal copy constructor only when I use brace initialization. My theory is based on the observation that when brace initialization is used to initialize a std::initializer_list object, the values in the braced list are copied into a temporary rvalue array. There are no std::initializer_list objects in my code, but let's suppose, for reasons I cannot fathom, that the use of auto in the above code somehow prompts gcc to treat a braced initializer as if it were being used with a std::initializer_list.
There are std::initializer_list objects in my code, because, as a prospective guideline for Effective C++11 puts it, "Remember that auto + {} == std::initializer_list." The statement
auto wcopy {w};
creates an object of type std::initializer_list<Widget>, not, as I had thought when I wrote the original post, an object of type Widget.If that were the case, the to-be-copied Widget would be copied into the array, where it would be an rvalue that would then be moved into the object being initialized. That is, given
auto wcopy {w};w would be copied into an array, and a request would then be made to move this array element into wcopy. But in the code where I declare no move constructor, no move constructor can be generated, because I've declared a copy constructor, and if you declare a copy operation, no move operators are generated. So in the code that requests a move of the rvalue in the array, the move is accomplished by calling a copy operation.
Because the object in the array is a copy of the actual initialization argument, it's not const, even if what it's a copy of is. In this code,
const Widget cw; auto cwcopy {cw};though cw is const, the copy of cw in the array is not. That means that when the compiler has to choose between the copy constructor (which takes a const Widget& parameter) and the instantiated universal copy constructor (which, in this case, takes a Widget& parameter), the universal copy constructor is the better match.
Remember, this is all a theory. But it does explain the output from gcc. In particular, it explains:
- Why there is an extra call to a copying operation: because the first copy operation copies the initializing object into the array, and the second one copies it from the array to the target of the initialization.
- Why the extra call is to the universal copy constructor: because any cv qualifiers present on the initializing object are absent from the copy in the array, and the universal copy constructor is a better match than the conventional copy constructor for non-const objects.
- Why the extra call goes away when I declare a move constructor: because then the move constructor can be called on the rvalue in the array, but calls to copy constructors and move constructors can be optimized away under certain conditions (which are fulfilled here). Such call elision is generally not permitted for other functions, including the universal copy constructor. Which means that when the move constructor is missing and cannot be generated, the universal constructor must be called, but when the move constructor is present, it need not be called.
If my theory is correct, I think it identifies a bug in gcc, because, as far as I know, the only time the contents of a braced initializer list are copied into a temporary array is when they're being used with a std::initializer_list object. (It may happen in calls to std::async and the variadic std::thread constructor, too, but I'm too lazy to look it up right now.)
I no longer believe that this is a bug in gcc, because gcc is doing exactly what I'd guessed: initializing a std::initializer_list<Widget> object. I just didn't think it was supposed to. Now I do.Anyway, bottom line: in C++98, copy constructors could not be generated from templates, but in C++11, they can be, practically speaking. At least that's my current understanding. Thanks to Martinho Fernandes for his post that sent me down this particular rabbit hole.
Thanks also to Xeo for pointing out the error in my original post.Scott
The first two instances of template Widget(const T&&) have a "const" that shouldn't be there, since that would disable the universal references, correct?
ReplyDelete'auto' together with any form of list-initialization ('{ ... }' or '= { ... }') will alway be deduced as 'std::initializer_list'.
ReplyDeleteOther than that, yeah, I think many people stumble upon this when they start using universal references. It's unfortunate, but luckily easy to fix with a 'is_related' (or 'is_bare_same') trait.
@Rein Halbersam: Right, thanks for the correction. I've fixed the post.
ReplyDelete@Xeo: Duh! You're right, of course, thanks for reminding me of this. I'll update the post when I have a bit more time. At least my musing about std::initializer_list was on target...
ReplyDeleteScott,
ReplyDeleteI am not acquainted with the Blogger formatting interface, but could you persuade it to format inline code with <code> rather than <span style="font-family: "Courier New",Courier,monospace;">? The relation between the two are like std::accumulate to std::for_each in C++; one is a specific means to a clearly expressed end, the other is a universal but semantically bare way to achieve some result.
@Yuri Khan: Currently, I'm just applying Courier to the text. I could go to the HTML view and manually enter the code tags, but that's be a lot less convenient for me. As a reader, all you're supposed to care about is what it looks like, not how that end is achieved. Can you elaborate on why you care which approach I take?
ReplyDeleteYes, to a certain extent, all I care is what it looks like. And for me, on your blog, the blocks of code appear in my favorite Liberation Mono (because they are in <pre>, which uses the face specified in browser preferences), while inline code appears in Courier (as explicitly specified by you). It’s very jarring visually.
ReplyDeleteBut if Blogger does not give you a button for correctly marking up code, and you are used to the WYSIWYG interface, oh well. I (and the remaining 0.3% of geeks who don’t like default typefaces) will have to write a userstyle or cope with Courier.
Very interesting article Scott. I always learn someting when I read your stuff, but I have a maybe stupid doubt that I'd like to iron out: wouldn't declaring the "Generalized Widget copy ctor" as
ReplyDeletetemplate
Widget(T&) {...} //Note: non-const T&
give you the same preferred-against-the-implicitly-declared-cctor behavior when starting from a non-const widget during the copy?
If that's true, since this doesn't need any universal reference, doesn't it imply that, going a bit against what you say in your bottom line, copy constructors _could_ be generated from templates even in C++98?
I'm just checking if my understanding/reasoning is correct...
Thanks,
Andrea.
Thanks for an interesting read. For why the extra move is done, I am reading the corresponding bullet at 8.5.4p3 that describes initialization of "std::initializer_list" from "{w}".
ReplyDeleteThe normative text says "Each element of that array is copy-initialized with the corresponding element of the initializer list".
With this description, no extra move should be done, but just one copy. Hence the output should just be "Universal Widget ctor".
BUT the note just below that normative text shows "roughly equivalent" code as follows (replacing "double" by "Widget")
Widget __a[1] = {Widget{w}};
In THAT code, there is first a copy, and then a move into that array. But it is non-normative and only non-normatively "roughly equivalent". Given its output, I would guess that GCC implements it like that, instead of the exact normative semantics (I did not run a GCC with your code, I'm talking about the version you are using, here).
If Clang omits the move, I would say it implements it correct, while GCC does it incorrectly.
@Yuri Khan: I did some googling around to see if I could find a way to add a "<code>" button to the editor toolbar, but so far I've come up empty. If somebody knows how I can do this with Blogger, please let me know.
ReplyDelete@abigagli: Your analysis is correct, but such a template could not be used to copy const objects. As such, the template would generate a very uncommon kind of copy constructor. (Such copy constructors are among the edge cases I set aside as not being worth frittering away time on.)
ReplyDeleteGreat article.
ReplyDeleteHonestly, this has got me thinking again about how much I hate that the brace initialization notation "binds tighter" to initializer_list than to normal uniform initialization.
From wikipedia C++11 article:
"Uniform initialization does not replace constructor syntax. There are still times when constructor syntax is required. If a class has an initializer list constructor (TypeName(initializer_list);), then it takes priority over other forms of construction, provided that the initializer list conforms to the sequence constructor's type. The C++11 version of std::vector has an initializer list constructor for its template type. This means that the following code:
std::vector the_vec{4};
will call the initializer list constructor, not the constructor of std::vector that takes a single size parameter and creates the vector with that size. To access the latter constructor, the user will need to use the standard constructor syntax directly."
So right now we have:
T t(1); <- unambiguously constructor call to T(int)
T t({1}); <- unambiguously constructor call to T(initializer_list)
T t{1}; <- ambiguous: initializer_list or int?
T t{{1}}; <- initializer_list
T t = 1; <- int
T t = {1}; <- initializer_list
auto t = {1}; <- initializer_list
auto t{1}; <- initializer_list
But if instead brace notation bound tighter to uniform initialization:
T t(1); <- unambiguously constructor call to T(int)
T t({1}); <- unambiguously constructor call to T(initializer_list)
T t{1}; <- unambiguous now: int
T t{{1}}; <- initializer_list
T t = 1; <- int
T t = {1}; <- initializer_list
auto t = {1}; <- initializer_list
auto t{1}; <- int
I think that the notation "t = {}" would always create an initializer_list, and "t{}" should always uniform initialize. I think it also solves the issues uniform initialization was introduced for (fixing most vexxing parse, and c-like syntax for initializing containers etc)
Maybe this isn't fully thought through, and I've obviously skipped over all the corner cases, but I feel like what we have now isn't perfect.
Scott: Willing to buy a blog post on "Non-uniform uniform initialization" ;)
Scott, in your first occurrence of Widget with default copy constructor:
ReplyDeleteclass Widget {
public:
Widget(const T&); // copy constructor (compiler-generated)
template
Widget(T&&); // universal copy constructor
// ...
};
It should not be "const T&" but instead "const Widget&" in the compiler-generated code I think.
Same for the code just below.
@Sebastien.F: Yes, you're right, thanks for pointing this out. I've updated the post to fix these bugs.
ReplyDeleteYou draw an artificial distinction between C++98 and C++11, by comparing Widget(const T&) to Widget(T&&). If you drop the const in the C++98 version the template will be instantiated to copy a non-const Widget, just like in C++11.
ReplyDelete@Jonathan Wakely: I'm not sure what part of the post you are referring to. What you say is true, as far as it goes, but declaring a template taking a T& parameter in C++98 won't permit binding to rvalues, so it's behaviorally quite different from a C++11 T&& parameter. Note that for most of my post, the copy constructor taking a const T& was generated by the compiler, and that's why I used that signature.
ReplyDelete