I know that NRVO allows a function to construct an object and return that object by value without the cost of a copy or even move operation. It found that it also works with nested function calls, allowing you to construct the object from the return value of another function call.
Please consider the following program and it's output as shown in the comments:
(Output from Visual Studio 2017, version 15.2, release build.)
#include <stdio.h>
class W
{
public:
W() { printf( "W::W()\n" ); }
W( const W& ) { printf( "W::W( const W& )\n" ); }
W( W&& ) { printf( "W::W( W&& )\n" ); }
W& operator=( const W& ) { printf( "W::operator=( const W& )\n" ); }
W& operator=( W&& ) { printf( "W::operator=( W&& )\n" ); }
~W() { printf( "W::~W()\n" ); }
void Transform() { printf( "W::Transform()\n" ); }
void Run() { printf( "W::Run()\n" ); }
};
W make()
{
W w;
return w;
}
W transform_make()
{
W w{ make() };
w.Transform();
return w;
}
W transform1( W w )
{
w.Transform();
return w;
}
W&& transform2( W&& w )
{
w.Transform();
return std::move(w);
}
int main() // Program output:
{
printf( "TestM:\n" ); //TestM:
{ //W::W()
W w{ make() }; //W::Run()
w.Run(); //W::~W()
}
//TestTM:
printf( "TestTM:\n" ); //W::W()
{ //W::Transform()
W w{ transform_make() }; //W::Run()
w.Run(); //W::~W()
}
//TestT1:
printf( "TestT1:\n" ); //W::W()
{ //W::Transform()
W w{ transform1( make() ) }; //W::W( W&& )
w.Run(); //W::~W()
} //W::Run()
//W::~W()
printf( "TestT2:\n" ); //TestT2:
{ //W::W()
W&& w{ transform2( make() ) }; //W::Transform()
w.Run(); //W::~W()
} //W::Run()
}
TestM
is the normal NRVO case. The object W
is constructed and destructed only once.
TestTM
is the nested NRVO case. Again the object is constructed only once and never copied or moved. So far so good.
Now getting to my question - how can I make TestT1
work with the same efficiency as TestTM
? As you can see in TestT1
a second object is move constructed - this is something I would like to avoid. How can I change the function transform1()
to avoid any additional copies or moves? If you think about it, TestT1
is not that much different from TestTM
, so I have a feeling that this is something that must be possible.
For my second attempt, TestT2
, I tried passing the object via RValue reference. This eliminated the extra move constructor, but unfortunately this causes the destructor to be called before I am done with the object, which is not always ideal.
Update:
I also note that it is possible to make it work using references, as long as you make sure not to use the object beyond the end of the statement:
W&& transform2( W&& w )
{
w.Transform();
return std::move(w);
}
void run( W&& w )
{
w.Run();
}
printf( "TestT3:\n" ); //TestT3:
{ //W::W()
run( transform2( make() ) ); //W::Transform()
} //W::Run()
//W::~W()
Is this safe to do?