It is known that std::move
should not be applied to the function return values because it can prevent RVO (return value optimization). I am interested in the question what should we do if we certainly know that RVO will not happen.
This is what the C++14 standard says [12.8/32]
When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. — end note ]
Here is the explanation from the book Effective Modern C++
The part of the Standard blessing the RVO goes on to say that if the conditions for the RVO are met, but compilers choose not to perform copy elision, the object being returned must be treated as an rvalue. In effect, the Standard requires that when the RVO is permitted, either copy elision takes place or std::move is implicitly applied to local objects being returned
As I understand when return object can't be elided at first it should be regarded as rvalue
. In these example we can see that when we pass argument greater than 5
object is moved otherwise it is copied. Does it mean that we should explicitly write std::move
when we know that RVO will not happen?
#include <iostream>
#include <string>
struct Test
{
Test() {}
Test(const Test& other)
{
std::cout << "Test(const Test&)" << std::endl;
}
Test(Test&& other)
{
std::cout << "Test(const Test&&)" << std::endl;
}
};
Test foo(int param)
{
Test test1;
Test test2;
return param > 5 ? std::move(test1) : test2;
}
int main()
{
Test res = foo(2);
}
The output of this program is Test(const Test&)
.
What happen in your example is not linked to RVO, but to the ternary operator ?
. If you rewrite your example code using an if
statement, the behavior of the program will be the one expected. Change foo
definition to:
Test foo(int param)
{
Test test1;
Test test2;
if (param > 5)
return std::move(test2);
else
return test1;
}
will output Test(Test&&)
.
What happens if you write (param>5)?std::move(test1):test2
is:
test2
pass through lvalue-to-rvalue conversion which causes copy-initialization as required in [expr.cond]/6
So in your example code, move elision occurs, nevertheless after the copy-initialization required to form the result of the ternary operator.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With