Search code examples
c++movervalue-referencervoxvalue

Is a rvalue reference parameter that is returned by value an xvalue?


My understanding is that, in the following function, the expression foo in the statement return foo; is an xvalue, because the object it denotes is expiring (even though foo is an lvalue in previous statements):

Foo bar()
{
    Foo foo;
    change(foo);
    return foo;
}

Such an expiring value is not covered by What expressions create xvalues?.

Does that change in the following case?

Foo bar(Foo&& foo)
{
    change(foo);
    return foo;
}

Is foo an xvalue in the return statement? And in particular, is it a candidate for move? And for RVO? Or should one use return std::move(foo)?

I do not know what the formal rule is for classifying the expression foo as an xvalue in the return statement of the first case, so I cannot test it in the second.


Solution

  • In that function, foo is an lvalue of type "rvalue reference to Foo". When you return it, since a copy has to be constructed (due to the type of the returned value), you are constructing a whole new value, which makes bar(...) an prvalue, as per §3.10.1.5:

    A prvalue (“pure” rvalue) is an rvalue that is not an xvalue. [ Example: The result of calling a function whose return type is not a reference is a prvalue. The value of a literal such as 12, 7.3e5, or true is also a prvalue. — end example ]

    Due to the fact that, inside the function, foo is an lvalue, the expression return foo is not a candidate for a move construction and the copy constructor is selected.

    And yes, RVO applies here, assuming a move is not selected first. There's nothing special in that regard here. As per §12.8.31:

    This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

    • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv- unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

    [...]


    To clarify, foo per se is an lvalue, but the statement:

    return foo;
    

    ultimately results (from the bar(...) expression) in a prvalue due to the fact that given that return type, the expression is equivalent to:

    return Foo(foo);
    

    which means that a temporary value, copied from foo is returned from the function bar.


    Rereading the reply, it still does not make sense to me. You say Due to the fact that, inside the function, foo is an lvalue, the expression return foo is not a candidate for a move construction and the copy constructor is selected. Why is this true in one case and not the other?

    When returning foo you have to create a new Foo value (because you are returning a copy) from the lvalue reference foo. This is done implicitly by the copy constructor. So return foo; is equivalent to return Foo(foo). Given that foo is an lvalue, the copy constructor is selected (and not the move constructor).

    Now, when you have this new temporary value (constructed from foo), the value itself, which comes out of the expression bar(...), is a prvalue. So when you do:

    auto res = bar(...);
    

    You have to construct a Foo copy out of a prvalue. Since a prvalue is also an rvalue, the constructor with the rvalue reference parameter (move constructor) is selected.