Here is a complete program:
#include
using std::cout;
using std::endl;
using std::move;
int count {0}; // global for monitoring
class Triple
First of all, understand that RVO and NRVO are opportunities offered by the writers of the standard to writers of compilers. A given compiler is free to ignore the possibility of RVO or NRVO if it can't make it work, if it doesn't know if it can make it work, if the moon is full, etc.
In this case, though, it's easy. The way (N)RVO is fundamentally implemented, is by constructing the return value directly into the memory occupied by the return value, or even the memory occupied by the variable which will be set to that return value.
That is, the potential savings from (N)RVO come not just from the ability to elide copy construction, but the ability to reduce copying in general.
But when the source of the return value is a function parameter, it's too late for that. left
is already somewhere in memory, and the return value has to go somewhere else. Short of some brute-force as-if-ruling from inlining, copying is already a given, because a second object has already been constructed.
If copy elision is disabled, both cases consist of 1 copy followed by 3 moves. (Any output of 2
for your code would indicate a compiler bug).
The copy is:
left
and the moves are:
left
a+b
from return valueresult
from a+b
It would be more illuminating to replace count
with an output message saying "in copy constructor"
and "in move constructor"
. Currently you are not tracking moves at all.
In the pass-by-reference case , all 3 moves can be elided. In the pass-by-value case, 2 of the moves can be elided. The move which cannot be elided is the move from left
to the return value.
I don't know the rationale for why this move can't be elided. Maybe it'd be difficult for a compiler to do something like A a = foo( A() );
if A()
were elidable all the way up to a
.