Lifetime extension, prvalues and xvalues
Asked Answered
M

2

7

Following the well accepted answer to this question Do rvalue references allow dangling references? It would seem that xvalues do not have their lifetime extended when assigned to a rvalue reference lvalue like in the question. However when I do this

#include <iostream>

using namespace std;

class Something {
public:
    Something() {
        cout << "Something()" << endl;
    }
    Something(const Something&) {
        cout << "Something(const Something&)" << endl;
    }
    Something(Something&&) {
        cout << "Something(Something&&)" << endl;
    }
    ~Something() {
        cout << "~Something()" << endl;
    }

    int a;
};

Something make_something() {
    return Something{};
}

int main() {
    auto&& something = make_something().a;

    return 0;
}

The lifetime of the object returned by a call to make_something is extended, even though make_something().a is an xvalue as per http://en.cppreference.com/w/cpp/language/value_category (the third bullet in the xvalues explanation lists the member access I have above as an xvalue,)

a.m, the member of object expression, where a is an rvalue and m is a non-static data member of non-reference type;

If value categories do not determine when the lifetime of an rvalue will be extended then what does? I am having a hard time understanding when the lifetime of an rvalue is extended in C++

Middleoftheroad answered 24/2, 2017 at 15:0 Comment(0)
I
7

Lifetime extension doesn't care about value categories. As stated by [class.temporary]/p6:

The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference

Emphasis added.

This says nothing here about the value category of the expression being referenced.

What determines whether a temporary is extended is exactly the above (and a few more rules).


But that does not explain why adding an std::move() around the temporary to which the reference is being assigned does not extend the lifetime

std::move is not a magical, compiler-defined construct in C++. It is a function call, and therefore it behaves no differently from any other C++ function call.

So, if you have std::move(Type()), what does that mean? It means that you will create a temporary, bind it to the parameter of std::move, then call that function, which will return something.

Binding a temporary to a function parameter, as stated in [class.temporary]/p6, means that the lifetime of the temporary is fixed to be the lifetime of the full expression that created it (if not for that rule, then the temporary would have to be destroyed at the end of the function call, since that's the end of the reference's lifetime).

It doesn't matter what the function does, says, or implies. It doesn't matter if the compiler could perhaps inline things and determine that the return value is a reference to an argument that came from a temporary. The lifetime of that temporary is fixed to the expression, not extended.

Intersperse answered 24/2, 2017 at 15:2 Comment(15)
I updated my question to include another part at the end.Middleoftheroad
But that does not explain why adding an std::move() around the temporary to which the reference is being assigned does not extend the lifetimeMiddleoftheroad
@Middleoftheroad Um... why not? Have you really thought about what "adding an std::move() around the temporary" actually means?Intersperse
Maybe not enough, I just thought that it converts the expression to an xvalue which is moveable and thus can be bound to an rvalue reference. Is there something I missed here?Middleoftheroad
So in essence lifetime extensions are not chained, the return type of the std::move() call extends the lifetime of whatever was passed to the lifetime of the return value, but since the return value expires right after the expression which called std::move() finishes. The lifetime of the value returned by std::move() is not extended. Did I get that right?Middleoftheroad
And the "temporaries" mentioned in the excerpt include but are not limited to prvalues, and the result of a std::move() is not considered a "temporary" and thus does not have its lifetime extended. xvalues can be "temporaries" like I have in the question above.Middleoftheroad
@Curious: "the return type of the std::move() call extends the lifetime of whatever was passed" I have no idea how you got that idea from what I said. move doesn't extend anything. The fact that a temporary is bound to a function parameter means that the lifetime of the temporary will be the lifetime of the full expression that the function call is a part of. It doesn't matter if that function is std::move or something else. This is true for all function calls where temporaries are bound to their parameters.Intersperse
@Curious: "The lifetime of the value returned by std::move() is not extended." Values do not have their lifetimes extended; values do not have lifetimes at all. Objects have lifetimes, and temporary objects can have their lifetimes extended. The return value of move is a reference, not an object.Intersperse
I had said "the return type of the std::move() call extends the lifetime of whatever was passed to the lifetime of the return value", which is the same thing as saying "the lifetime of the temporary will be the lifetime of the full expression that the function is a part of" right? Since the return value of the move() has the same lifetime as the expression, and thus whatever was passed in will live only as long as the expressionMiddleoftheroad
@Curious: No, that's not the same thing at all. The return value has nothing to do with it. It's the binding of a temporary to a reference parameter that sets the temporary's lifetime. I have no idea where you're getting the return value from in this discussion, since I never mentioned that.Intersperse
You didn't say that but isn't it true that the temporary passed to a function call will persist till the reference to it (if the function returns one) has expired? Therefore when std::move() returns a reference, the temporary passed to std::move() will live until that reference does, which itself is the lifetime of the expression.Middleoftheroad
Let us continue this discussion in chat.Intersperse
Could you include the definition of temporaries from SebTu's answer as well? I'll accept this thenMiddleoftheroad
@Curious: I'm not sure how the definition of where temporaries come from relates to the way their lifetimes are extended.Intersperse
It does make sense in context. And it would be a nice thing to have in the accepted answer. Since some might be confused about prvalues and temporaries and that sort of stuffMiddleoftheroad
A
1

If value categories do not determine when the lifetime of an rvalue will be extended then what does? I am having a hard time understanding when the lifetime of an rvalue is extended in C++

Note that value categories describe expressions not objects. Value categories ( xvalue, prvalue, or whatever ) won't be extended in any way. Only objects can have a lifetime.

From the n4296 standard draft:

  • §12.2.1

    Temporaries of class type are created in various contexts: binding a reference to a prvalue (8.5.3), returning a prvalue (6.6.3), a conversion that creates a prvalue (4.1, 5.2.9, 5.2.11, 5.4), throwing an exception (15.1), and in some initializations (8.5).

and

  • §12.2.4

    There are two contexts in which temporaries are destroyed at a different point than the end of the full- expression. [...]
    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 subobject to which the reference is bound persists for the lifetime of the reference

Note: I didn't quote the first context since it has minor revelance regarding the questions. Italic emphasis added by me.

Thus the value category of the function expression makesomething() is a prvalue creating a temporary of class type, according to the first paragraph cited above.
makesomething().a accesses a temporary, complete subobject. Binding this temporary to a reference leads, according to the second context quoated above, to an extended lifetime.


The lifetime of the subobject a is coupled to the lifetime of the previous created temporary making it an expiring value (xvalue). Without extending its lifetime by binding it to a reference it would be destroyed together with the temporary class object. Thus, in this case, after the ;.

Aubree answered 24/2, 2017 at 15:23 Comment(4)
Could you paste the first context that §12.2.4 talked about as well?Middleoftheroad
@Middleoftheroad it is really just a language subtlety with minor importance. You can look it up in the standard if you are interested. Adding it to the answer will be confusing.Aubree
@Middleoftheroad take a look at this answer from Nicol Bolas. It clarified the topic a lot for me https://mcmap.net/q/15524/-what-are-rvalues-lvalues-xvalues-glvalues-and-prvaluesAubree
That was one of the first things I read when reading about xvalues, prvalues and glvalues :)Middleoftheroad

© 2022 - 2024 — McMap. All rights reserved.