I am confused with regards how do compiler and linker deal with the fact that requirements on the caller of the function differ depending on if the function uses RVO or NRVO.
This could be my misunderstanding, but my assumption is that generally without RVO or NRVO
std::string s = get_string();
involves move construction of s from result of get_string if get_string does not do N?RVO but if get_string does N?RVO calling code does nothing and s
is constructed inplace by the function get_string.
EDIT: here is how I imagine get_string caller operating if there is no N?RVO:
and now with RVO
The caller allocates space for the return object no matter what. From the caller's perspective, it doesn't matter if the function uses RVO or not.
You're also confusing two separate copy elisions. There's RVO, which elides the copy from a function local variable to the return value, and there's another copy from the function return value to the object being initialized that is also often elided.
Basically, without any elision, you can think of the call from the OP as looking something like this (ignore any aliasing issues, this would all actually be implemented directly in assembly):
void get_string(void* retval)
{
std::string ret;
// do stuff to ret
new(retval) std::string(std::move(ret));
}
char retval[sizeof(std::string)];
get_string(retval);
std::string s(std::move(*(string*)retval));
The string ret
is copied (or moved, in this case) twice: once from ret
to the retval
buffer, and once from retval
to s
.
Now, with NRVO applied, only the definition of get_string
would change:
void get_string(void* retval)
{
std::string& ret = *new(retval) std::string;
// do stuff to ret
}
From the caller's perspecitve, nothing has changed. The function just directly initializes the object it's going to return into the space allocated by the caller for the return value. Now the string is only moved once: from retval
to s
.
Now the caller can also elide a copy, since there's no need to allocate a separate return value and then copy it into the object being initialized:
char retval[sizeof(std::string)];
get_string(retval);
std::string& s(*(string*)retval);
In this way, s
is initialized directly by get_string
, and no copies or moves are performed.