My question originates from delving into std::move in return statements, such as in the following example:
struct A
{
A() { std::cout << "Constructed " << this << std::endl; }
A(A&&) noexcept { std::cout << "Moved " << this << std::endl; }
};
A nrvo()
{
A local;
return local;
}
A no_nrvo()
{
A local;
return std::move(local);
}
int main()
{
A a1(nrvo());
A a2(no_nrvo());
}
which prints (MSVC, /std:c++17, release)
Constructed 0000000C0BD4F990
Constructed 0000000C0BD4F991
Moved 0000000C0BD4F992
I am interested in the general initialization rules for return statements in functions that return by-value and which rules apply when returning a local variable with std::move as shown above.
The general case
Regarding return statements you can read
- Evaluates the expression, terminates the current function and returns the result of the expression to the caller after implicit conversion to the function return type. [...]
on cppreference.com.
Amongst others Copy initialization happens
- when returning from a function that returns by value like so
return other;
Coming back to my example, according to my current knowledge - and in contrast to the above-named rule - A a1(nrvo()); is a statement that direct-initializes a1 with the prvalue nrvo(). So which object exactly is copy-initialized as described at cppreference.com for return statements?
The std::move case
For this case, I've referred to ipc's answer on Are returned locals automatically xvalues. I want to make sure that the following is correct: std::move(local) has the type A&& but no_nrvo() is declared to return the type A, so here the
returns the result of the expression to the caller after implicit conversion to the function return type
part should come into play. I think this should be an Lvalue to rvalue conversion:
A glvalue of any non-function, non-array type T can be implicitly converted to a prvalue of the same type. [...] For a class type, this conversion [...] converts the glvalue to a prvalue whose result object is copy-initialized by the glvalue.
To convert from A&& to A A's move constructor is used, which is also why NRVO is disabled here. Are those the rules that apply in this case, and did I understand them correctly? Also, again they say copy-initialized by the glvalue but A a2(no_nrvo()); is a direct initialization. So this also touches on the first case.