Search code examples
c++c++11referencervalue-referencelifetime

What determines when the lifetimes of temporaries get extended into const references or rvalue references?


Given:

struct hurg { ... };

hurg get_hurg() { return hurg(); }
hurg&& get_mhurg() { return hurg(); }

My understanding and experimenting shows that the following is not undefined behavior (EDIT: thanks to the answers, it turns out I was wrong and the get_mhurg() examples are undefined behavior):

{
    const hurg& a = get_hurg(); 
    hurg&& b = get_hurg();
    const hurg& c = get_mhurg(); 
    hurg&& d = get_mhurg();
    // do stuff with a, b, c, d
}
// a, b, c, d are now destructed

That is, the lifetime of the temporary hurg object returned by get_hurg() and get_mhurg() is extended until the end of the scope.

However, in the case of (function from here):

template <typename T>
auto id(T&& x) -> decltype(auto) { return decltype(x)(x); }    

Using it like:

{
    const hurg& x = id(hurg()); 
    // the hurg() 'x' refers to is already destructed

    hurg&& y = id(hurg());
    // the hurg() 'y' refers to is already destructed

    // undefined behavior: use 'x' and 'y'
}

In this case, the lifetime of the hurg is not extended.

What determines when the lifetime of temporaries is extended in general? And, in particular, when is it safe to bind the result of a function to a const lvalue ref or an rvalue ref?

And more specifically, what precisely is happening in the id case?


Solution

  • From [class.temporary]:

    There are two contexts in which temporaries are destroyed at a different point than the end of the fullexpression. The first context is when a default constructor is called to initialize an element of an array [...]

    The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a sub-object to which the reference is bound persists for the lifetime of the reference except:
    (5.1) — A temporary object bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.
    (5.2) — The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
    (5.3) — A temporary bound to a reference in a new-initializer (5.3.4) persists until the completion of the full-expression containing the new-initializer.

    So two things. First, get_mhurg is undefined behavior. The lifetime of the temporary you're returning is not extended. Second, the temporary passed into id lasts until the end of the full-expression containing the function call, but no further. As with get_mhurg, the temporary is not through-extended. So that would also be undefined behavior.