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.