I am looking at copy elision and RVO/NRVO. When I run g++ with -fno-elide-constructors
I see different behaviour for c++11 and c++17.
I understand that c++17 mandates RVO under certain circumstances. I would have assumed that the below counts as NRVO (unless the compiler first eliminates the named variable y
too?)
struct rvo
{
rvo () : val {0} { std::cout << "Default Constructor" << std::endl;};
~rvo (){ std::cout << "Destructor" << std::endl; };
rvo(const rvo& in){ std::cout << "Copy Constructor" << std::endl; }
rvo(rvo&& in){ std::cout << "Move Constructor" << std::endl; }
double val;
};
rvo f1(rvo& x){
rvo y {x};
y.val++;
return y;
}
int main()
{
rvo A{};
rvo B {f1(A)};
return 0;
}
Output when compiled up with no additional flags is as expected
Default Constructor
Copy Constructor
Destructor
Destructor
When compiled up with -fno-elide-constructors
and --std=c++11
the output is
Default Constructor
Copy Constructor
Move Constructor
Destructor
Move Constructor
Destructor
Destructor
Destructor
And this is fine as the temporary is being created.
My question is that when I compile up with -fno-elide-constructors
and --std=c++17
, it eliminates one of the moves.
Default Constructor
Copy Constructor
Move Constructor
Destructor
Destructor
Destructor
It appears that it is eliminating the temporary return variable, and doing a move from the function variable y
back to B
in main. Even though I have switched off elision. Is it considering it as mandatory RVO or am I misunderstanding something basic?
I found this example, RVO/NRVO can not be disabled in C++17 in Clang++? but that one is obvious RVO whereas I thought my example was NRVO and thus not mandatory.
Thanks very much if anyone can confirm.
This is because from c++17 there is mandatory copy elision and instead the object B
is created directly from the return value f(A)
so that there is no use of a move constructor or a copy ctor and hence the output.
This can be seen from copy elision documentation:
Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible:
- In the initialization of an object, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type:
T x = T(T(f())); // only one call to default constructor of T, to initialize x