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
C
has a user-defined destructor, an implicit move constructor is not generated. Thus, overload resolution will select the templated constructor withArgs
deduced 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
=C
to 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 oneC
object is constructed. HereC
's move constructor is implicitly generated and selected by overload resolution, and you see RVO kick in again.