Explicit temporary lifetime extension

206 views
Skip to first unread message

Nicol Bolas

unread,
Jan 14, 2017, 10:20:59 AM1/14/17
to ISO C++ Standard - Future Proposals
This is something of a fork of the thread on unnamed variables through structured binding. Several posts were made about using an explicit lifetime extension mechanism to do more-or-less the same job.

I wanted to fork that discussion over here because a couple of interesting points were brought up with regard to lifetime extension. Such points have nothing to do with unnamed variables, but I felt that they needed to be discussed:

1: A lifetime extended temporary can no longer be a prvalue. This is due to guaranteed elision requirements; prvalues aren't necessarily temporaries. Lifetime extension extends the lifetime of temporaries. So you need to manifest a temporary when you extend a prvalue's lifetime. And thus the result is not a prvalue.

2:

On Friday, January 13, 2017 at 1:43:20 PM UTC-5, Zhihao Yuan wrote:
On Fri, Jan 13, 2017 at 10:23 AM, Matthew Woehlke
<mwoehlk...@gmail.com> wrote:
>
> ...in particular, it would be nice for this to finally work:
>
>   for (auto i : std::add_const(__extend_me make_me_a_list())

`add_const` banned rvalue.

What is the value category of an extended temporary? It cannot be a prvalue, as previously stated. So that leaves 3 options:

1: xvalue

2: lvalue

3: Some new value category. Please no; don't we have enough?

I would say that lifetime extension should be conceptually a conversion from:

(__extend_me expr1) op expr2

to

auto &&unnamed = expr1
unnamed op expr2

If that is the meaning of lifetime extension, then it is quite clear that the extended expression is an lvalue, not an xvalue. It isn't eXpiring; it's just an unnamed variable that happens to reside in the middle of an expression. And variables are lvalues.

Therefore, `as_const(__extend_me expr)` should work just fine. You can move from an extended expression, but you need to use `std::move` for that explicitly.

Zhihao Yuan

unread,
Jan 14, 2017, 12:12:16 PM1/14/17
to std-pr...@isocpp.org
On Sat, Jan 14, 2017 at 9:20 AM, Nicol Bolas <jmck...@gmail.com> wrote:
> 1: A lifetime extended temporary can no longer be a prvalue. This is due to
> guaranteed elision requirements; prvalues aren't necessarily temporaries.
> Lifetime extension extends the lifetime of temporaries. So you need to
> manifest a temporary when you extend a prvalue's lifetime. And thus the
> result is not a prvalue.
>

Yes.

> On Friday, January 13, 2017 at 1:43:20 PM UTC-5, Zhihao Yuan wrote:
>>
>> `add_const` banned rvalue.
>
>
> What is the value category of an extended temporary? It cannot be a prvalue,
> as previously stated. So that leaves 3 options:
>
> 1: xvalue
>
> 2: lvalue
>

lvalue, but let me explain some details...

> I would say that lifetime extension should be conceptually a conversion
> from:
>
> (__extend_me expr1) op expr2
>
> to
>
> auto &&unnamed = expr1
> unnamed op expr2
>

Its semantics is equivalent to

decltype(auto) unnamed = expr1;
unnamed op expr2;

Note that when expr1 is a prvalue, decltype(expr1) = T,
decltype(unnamed) = T, but
decltype(__extend_me expr1) != decltype(unnamed),
because `__extend_me expr1` is an lvalue expression,
so the decltype(__extend_me expr1) = decltype((unnamed))
which is T&.

> Therefore, `as_const(__extend_me expr)` should work just fine. You can move
> from an extended expression, but you need to use `std::move` for that
> explicitly.

You are right. Me and my friends blueprinted __extend_me
(we call it `register`)'s semantics 2 months ago like what I
showed above, but during the discussion around `as_const`
I forgot. Sorry about that.

--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
___________________________________________________
4BSD -- http://blog.miator.net/

Scott Dolim

unread,
Jan 14, 2017, 6:02:52 PM1/14/17
to ISO C++ Standard - Future Proposals
I think it is worth putting a stake in the ground and *defining* the term "lifetime extension" as the act of converting a temporary's value category to lvalue. Since temporary variables are introduced by the compiler to hold transient intermediate values of a complex expression, those variables are xvalues already (materialized from prvalue, by necessity). Normally the transient value is meant to be immediately consumed by the operator of the term it appears in. As I recall, lifetime extension was added to C++ in order to handle cases the operator binds to the address of the temporary instead, so a dangling reference would result if it actually did expire immediately. The core idea being to delay the expiration past the point where any other part of the overall expression could witness it (which would mean seeing the temporary as an xvalue) so, as with the case of named rvalue reference variables, this therefore categorizes them as lvalue, for the duration the expression evaluation (or longer).

Basically, exactly what you said with the "auto&& unnamed" example, but flipping the logic so that the definition "lifetime extension = temporary is now an lvalue" is the axiom which implies the example as an exposition of how the mechanism is implemented.

Nicol Bolas

unread,
Jan 14, 2017, 6:40:23 PM1/14/17
to ISO C++ Standard - Future Proposals
On Saturday, January 14, 2017 at 6:02:52 PM UTC-5, Scott Dolim wrote:
I think it is worth putting a stake in the ground and *defining* the term "lifetime extension" as the act of converting a temporary's value category to lvalue.

An expression's value category does not determine its lifetime. A function which returns a `T&` returns an expression who's value category is lvalue. But that makes no statement about the lifetime of the object being referenced.

Thus, simply declaring that lifetime extension is equivalent to converting the expression to an lvalue is not sufficient.

Scott Dolim

unread,
Jan 14, 2017, 7:16:09 PM1/14/17
to ISO C++ Standard - Future Proposals
Doesn't a subexpression of type T&, even if that is returned from f(), imply that the value will not be destructed during the evaluation of the rest of the expression it is in? Granted, there is the classic bug of returning a ref to a local from inside f(), but that was destructed before the function even yielded the result. That bug aside, C++ will not (of its own accord) sequence an lvalue destruction during an expression the value appears in, even after it is consumed in a parent expression(that's what xvalues are for).

So I'm only referring to lifetime relative to the expression (or block, if we're talking about the explicit lifetime extension case). Meaning, an lvalue, if valid to begin with (on return from f, say) will continue to be valid for the duration of the subsequent evaluation where it is accessible, as far as the compiler inserting destructors is concerned. Or said another way, lvalue lifetimes are naturally scope-delimited. Compared to xvalues, which are disposable from the moment the containing expression is about to consume them, this is an extended lifetime.

I think I'm looking more at the validity of the T& "value" itself (the address where a T lives) than the object therein - granted one could throw in an explicit destructor call in the middle of the expression, but the idea is that when a temporary is created by the compiler, it is reserving space to hold a value, and we are (I think) talking about the lifetime of this reservation. An xvalue temporary's reservation is much shorter than an lvalue temporary's. Could be this correlation of value category to lifetime is about storage, not object, lifetime.

Nicol Bolas

unread,
Jan 14, 2017, 10:41:15 PM1/14/17
to ISO C++ Standard - Future Proposals
On Saturday, January 14, 2017 at 7:16:09 PM UTC-5, Scott Dolim wrote:
Doesn't a subexpression of type T&, even if that is returned from f(), imply that the value will not be destructed during the evaluation of the rest of the expression it is in?

No:

auto &str = std::string("f00").replace(1, 2, "Something New");

The return value of `replace` is a reference to the `string` it was called on. This is an lvalue. And yet, that does nothing to extend the lifetime of the temporary `this` that `replace` was given. `replace` will return `*this`, and therefore `str` will reference a deleted `string` after this statement executes.

A reference return value has a lifetime which is effectively in-determinant. Its lifetime (in code that works) is usually linked to the lifetime of one of its parameters, but we cannot know which one just from the function's parameters. You'd have to look at its implementation/documentation.

Which BTW is about 90% of the reason why we need explicit lifetime extension to begin with.

Granted, there is the classic bug of returning a ref to a local from inside f(), but that was destructed before the function even yielded the result.  That bug aside, C++ will not (of its own accord) sequence an lvalue destruction during an expression the value appears in, even after it is consumed in a parent expression(that's what xvalues are for).

Unless an expression includes an explicit destructor call or delete, C++ will never invoke a destructor during an expression. Whether it's an lvalue, prvalue, or xvalue, any object created during an expression will continue to exist until that expression has finished its evaluation. Period.

xvalues are not "consumed in a parent expression" either. `replace` could easily have returned an rvalue reference to `*this` via `std::move(*this)`. That's perfectly legal, and its return value would be an xvalue.

And it would change nothing about the lifetime of its return value. There is no correlation between object lifetime and value category.

An xvalue temporary's reservation is much shorter than an lvalue temporary's.


There's no such thing as an "xvalue temporary" or an "lvalue temporary". Pre-C++17, temporaries are always prvalues. Post-C++17, temporaries only exist as a result of the use of prvalue expressions. In neither case is a temporary object ever an xvalue or lvalue.

Scott Dolim

unread,
Jan 14, 2017, 11:06:16 PM1/14/17
to ISO C++ Standard - Future Proposals
Ah.  OK, I now realize that, as the saying goes, I've opened my mouth and removed all doubt.  But I don't think I'd ever have had a chance otherwise to clear up my obviously muddled understanding of the standard on this topic, particularly the terminology, without blurting it out.  Thanks much for the clear explanation.   

On the point of your original post, your conclusion that the lifetime-extended temporary ought to be treated like an unnamed variable which is an lvalue, does seem correct even if my attempted justification for it was total hogwash...  

Jens Maurer

unread,
Jan 15, 2017, 4:46:00 AM1/15/17
to std-pr...@isocpp.org
On 01/15/2017 12:02 AM, 'Scott Dolim' via ISO C++ Standard - Future Proposals wrote:
> I think it is worth putting a stake in the ground and *defining* the term "lifetime extension" as the act of converting a temporary's value category to lvalue. Since temporary variables are introduced by the compiler to hold transient intermediate values of a complex expression, those variables are xvalues already (materialized from prvalue, by necessity). Normally the transient value is meant to be immediately consumed by the operator of the term it appears in. As I recall, lifetime extension was added to C++ in order to handle cases the operator binds to the address of the temporary instead, so a dangling reference would result if it actually did expire immediately. The core idea being to delay the expiration past the point where any other part of the overall expression could witness it (which would mean seeing the temporary as an xvalue) so, as with the case of named rvalue reference variables, this therefore categorizes them as lvalue, for the duration the expressio
n evaluation (or longer).

12.2p4 says that temporary objects are usually destructed at the
end of the full-expression, not "immediately".

"Temporary objects are destroyed as the last step in evaluating the full-
expression (1.9) that (lexically) contains the point where they were created."

Jens

Scott Dolim

unread,
Jan 15, 2017, 1:36:17 PM1/15/17
to ISO C++ Standard - Future Proposals
Thanks Jens. I definitely had a misunderstanding of how temporary lifetimes are handled, thinking it was related somehow to the value category of the expression that the temporary was introduced for. Nicol cleared it up with some nice examples.
Reply all
Reply to author
Forward
0 new messages