Why does rvalue optimization 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
I believe that the problem is that instantiations of
are not copy/move constructors as far as the language is concerned and therefore calls to them cannot be elided by the compiler. From §12.8 [class.copy]/p2-3, emphasis added and examples omitted:
In other words, a constructor that is a template can never be a copy or move constructor.
The return value optimization is a special case of copy elision, which is described as (§12.8 [class.copy]/p31):
This allows implementations to elide "copy/move construction"; constructing an object using something that's neither a copy constructor nor a move constructor is not "copy/move construction".
Because
Chas a user-defined destructor, an implicit move constructor is not generated. Thus, overload resolution will select the templated constructor withArgsdeduced asC, which is a better match than the implicit copy constructor for rvalues. However, the compiler can't elide calls to this constructor, as it has side effects and is neither a copy constructor nor a move constructor.If the templated constructor is instead
Then it can't be instantiated with
Args=Cto produce a copy constructor, as that would lead to infinite recursion. There's a special rule in the standard prohibiting such constructors and instantiations (§12.8 [class.copy]/p6):Thus, in that case, the only viable constructor would be the implicitly defined copy constructor, and calls to that constructor can be elided.
If we instead remove the custom destructor from
C, and add another class to track whenC's destructor is called instead:We see only one call to
D's destructor, indicating that only oneCobject is constructed. HereC's move constructor is implicitly generated and selected by overload resolution, and you see RVO kick in again.