C ++ universal reference in constructor and return value optimization (rvo)

Why rvalue optimization does not occur in classes with constructor with universal reference arguments?

http://coliru.stacked-crooked.com/a/672f10c129fe29a0

#include <iostream> template<class ...ArgsIn> struct C { template<class ...Args> C(Args&& ... args) {std::cout << "Ctr\n";} // rvo occurs without && ~C(){std::cout << "Dstr\n";} }; template<class ...Args> auto f(Args ... args) { int i = 1; return C<>(i, i, i); } int main() { auto obj = f(); } 

Output:

 Ctr Ctr Dstr Ctr Dstr Dstr 
+8
c ++ rvalue-reference templates rvo universal-reference
source share
1 answer

I believe the problem is that the instances

 template<class ...Args> C(Args&& ... args) {std::cout << "Ctr\n";} 

are not copy / move constructors relative to the language, so calls to them cannot be eliminated by the compiler. From ยง12.8 [class.copy] / p2-3, selection is added and examples are omitted:

A constructor without a template 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 all other parameters have default arguments (8.3.6).

A constructor without a template for class X is a move 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 all other parameters have default arguments (8.3.6).

In other words, a constructor that is a template can never be a copy or move constructor.

Return value optimization is a special case of copying, which is described as (ยง12.8 [class.copy] / p31):

When certain criteria are met, an implementation may skip copying / moving an object of the class, even if the constructor of the selected copy / move and / or destructor operation for the object has side effects.

This allows copy / move implementations; creating an object using something that neither the copy constructor nor the move constructor are "copy / move".

Because C has a user-defined destructor, an implicit move constructor is not generated. Thus, overload resolution will choose a template constructor with Args , deduced as C , which is better match than the implicit copy constructor for rvalues. However, the compiler cannot invoke calls to this constructor, since it has side effects and is neither a copy constructor nor a move constructor.

If the template constructor is instead

 template<class ...Args> C(Args ... args) {std::cout << "Ctr\n";} 

Then it cannot be created using Args = C to create a copy constructor, as this will lead to infinite recursion. There is a special rule in the standard prohibiting such constructors and instances (ยง12.8 [class.copy] / p6):

The constructor declaration for class X poorly formed if its first parameter is of type (optionally cv-qualified) X and either there are no other parameters, otherwise all other parameters are arguments by default. A member function template is never created to create such a constructor signature.

Thus, in this case, the only viable constructor will be an implicitly defined copy constructor, and calls to this constructor can be deleted.

If we instead of a custom destructor from C and add another class to track when instead of C destructor is called:

 struct D { ~D() { std::cout << "D Dstr\n"; } }; template<class ...ArgsIn> struct C { template<class ...Args> C(Args&& ... args) {std::cout << "Ctr\n";} D d; }; 

We see only one call to the destructor D , indicating that only one C object is built. Here, the move constructor C implicitly generated and selected using overload resolution, and you see RVO again.

+9
source share

All Articles