Search code examples
c++c++11undefined-behaviorrvalue-reference

When does returning an rvalue reference result in undefined behavior?


In an Stack Overflow answer here, Kerrek posts the following code.

Foo && g()
{
    Foo y;
    // return y;         // error: cannot bind ‘Foo’ lvalue to ‘Foo&&’
    return std::move(y); // OK type-wise (but undefined behaviour, thanks @GMNG)
}

GManNickG points out that this results in undefined behavior.

Kerrek adds

True; you don't really return && from anything but forward and move. It's a contrived example.

What confuses me is that the C++11 standard uses a function call that returns an rvalue reference as an example of an expression that is an xvalue.

An xvalue (an “eXpiring” value) also refers to an object, usually near the end of its lifetime (so that its resources may be moved, for example). An xvalue is the result of certain kinds of expressions involving rvalue references (8.3.2). [ Example: The result of calling a function whose return type is an rvalue reference is an xvalue. —end example ]

So when exactly does returning an rvalue reference result in undefined behavior? Does it always result in undefined behavior except for std::move and std::forward and is the standard just being terse? Or would you have to access the return value for undefined behavior to result?

*By "when" I mean "under what circumstances". I realize that there's no meaningful concept of time here.


Solution

  • Casting into a reference does not extend the lifetime of the local variable.

    As such, using that reference to the local after the local expires is undefined behaviour.

    As the local expires when the function completes, there is no way to use the return value.

    std::move or std::forward takes a reference, and returns a reference of possibly different type. Neither extend the lifetime of their arguement, and neither return a reference to a variable local to their own body.

    The UB here is not returning an rvalue reference, but rather object lifetime based. It occurs on use, not on cast or return.

    That said, it is almost never a good idea to return an rvalue reference: return a copy instead. Copies can particiate in lifetime extension. The exception is when you are writing something like forward_as_tuple.