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.