Date: Mon, 15 Jul 2024 11:32:56 -0400
On Sat, Jul 13, 2024 at 11:08 AM Arthur O'Dwyer
<arthur.j.odwyer_at_[hidden]> wrote:
>
> On Sat, Jul 13, 2024 at 10:23 AM Jason McKesson via Std-Proposals <std-proposals_at_[hidden]> wrote:
>>
>>
>> Annotating the variable itself is important because guaranteed NRVO
>> means that the lifetime of the object will be extended beyond the
>> scope of the function.
>>
>> One of the biggest issues with guaranteed NRVO is that it
>> fundamentally changes the meaning of the code. A local variable is not
>> *local* anymore; it *will* persist beyond its evident scope.
>>
>> [...]
>> Your rules now require that this code works. The user is following the
>> rules of guaranteed NRVO, so `retvar` *must* persist beyond the scope
>> of the function call. As such, `ptr` *must* point to `var`.
>
>
> That's one very reasonable model for "guaranteed NRVO." I think (but I'm not sure off the top of my head) that that's the model used by P2025.
> But it's not the only possible model.
> Another possible model is that the lifetime of `var` really truly has ended, and a new object of the same type has begun its lifetime at the same address. Pointers and references to the old object `var` are invalidated at this point. However — by core-language magic — this has occurred without calling any constructor or destructor.
> This gets you your UB (and optimization/escape-analysis opportunity), and also your runtime efficiency, at the same time.
> But it requires wording to get there. Which P2025 attempted, and which this std-proposals thread certainly isn't attempting (and shouldn't be: if you have a real idea, put it in a paper or something).
> Relevant reading:
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109945
> https://cplusplus.github.io/CWG/issues/2868.html
> and probably several other CWG issues.
>
> my $.02,
> –Arthur
I don't think that this is just a matter of wording. It goes into the
core philosophy of what all of these constructs actually *mean*.
Guaranteed elision is able to get away with what it does because it
redefines the meaning of a prvalue. And the thing about prvalues is
that it is grammatically impossible to access the same prvalue
expression from two different places. To try to do that *requires*
creating an lvalue at some point, at which point you can materialize a
temporary or whatever.
Pre-guaranteed elision, a prvalue was an object. Post-guaranteed
elision, a prvalue became an initializer for an object. But that's OK
because how you use it defines whether a temporary is initialized or
whether some named object is initialized. The underlying meaning of
things changed, but not in a way that started to violate what any of
the code actually meant.
The prvalue expression could access any variables in its scope (local
to the function) even though the object it is initializing is outside
of that scope. How that works follows a clear philosophy. The
expression is in a scope, even if the object initialized by it is not.
So... what does it mean to do `return variable;` when the function
returns a prvalue of that type? `variable` is not an initializer; it
is an lvalue expression whose lifetime and scope are bound to that
function. So now we have to engage with the question of meaning here.
Does `variable` represent the same object as the return value or not?
Your idea, where `variable`'s lifetime ends but a new object's
lifetime begins in the same memory doesn't work, because `variable`
has a clear place in the order of destruction within the scope of that
function. The return value object needs to be initialized before the
lifetime of any local variables end. And other local variables should
be able to access any objects that are after themselves in that order
of destruction.
So doing what you suggest requires changing the order of
initialization and destruction of local variables. That's not
unreasonable. However, if you want to do that, it *must* happen
through explicit syntax.
This is why understanding the philosophy behind something like
guaranteed NRVO is so important. It exposes the issues of going from
lvalue to prvalue back to lvalue and the myriad of complexities around
it. You can't just gloss over it; it must be central to any such
proposal.
<arthur.j.odwyer_at_[hidden]> wrote:
>
> On Sat, Jul 13, 2024 at 10:23 AM Jason McKesson via Std-Proposals <std-proposals_at_[hidden]> wrote:
>>
>>
>> Annotating the variable itself is important because guaranteed NRVO
>> means that the lifetime of the object will be extended beyond the
>> scope of the function.
>>
>> One of the biggest issues with guaranteed NRVO is that it
>> fundamentally changes the meaning of the code. A local variable is not
>> *local* anymore; it *will* persist beyond its evident scope.
>>
>> [...]
>> Your rules now require that this code works. The user is following the
>> rules of guaranteed NRVO, so `retvar` *must* persist beyond the scope
>> of the function call. As such, `ptr` *must* point to `var`.
>
>
> That's one very reasonable model for "guaranteed NRVO." I think (but I'm not sure off the top of my head) that that's the model used by P2025.
> But it's not the only possible model.
> Another possible model is that the lifetime of `var` really truly has ended, and a new object of the same type has begun its lifetime at the same address. Pointers and references to the old object `var` are invalidated at this point. However — by core-language magic — this has occurred without calling any constructor or destructor.
> This gets you your UB (and optimization/escape-analysis opportunity), and also your runtime efficiency, at the same time.
> But it requires wording to get there. Which P2025 attempted, and which this std-proposals thread certainly isn't attempting (and shouldn't be: if you have a real idea, put it in a paper or something).
> Relevant reading:
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109945
> https://cplusplus.github.io/CWG/issues/2868.html
> and probably several other CWG issues.
>
> my $.02,
> –Arthur
I don't think that this is just a matter of wording. It goes into the
core philosophy of what all of these constructs actually *mean*.
Guaranteed elision is able to get away with what it does because it
redefines the meaning of a prvalue. And the thing about prvalues is
that it is grammatically impossible to access the same prvalue
expression from two different places. To try to do that *requires*
creating an lvalue at some point, at which point you can materialize a
temporary or whatever.
Pre-guaranteed elision, a prvalue was an object. Post-guaranteed
elision, a prvalue became an initializer for an object. But that's OK
because how you use it defines whether a temporary is initialized or
whether some named object is initialized. The underlying meaning of
things changed, but not in a way that started to violate what any of
the code actually meant.
The prvalue expression could access any variables in its scope (local
to the function) even though the object it is initializing is outside
of that scope. How that works follows a clear philosophy. The
expression is in a scope, even if the object initialized by it is not.
So... what does it mean to do `return variable;` when the function
returns a prvalue of that type? `variable` is not an initializer; it
is an lvalue expression whose lifetime and scope are bound to that
function. So now we have to engage with the question of meaning here.
Does `variable` represent the same object as the return value or not?
Your idea, where `variable`'s lifetime ends but a new object's
lifetime begins in the same memory doesn't work, because `variable`
has a clear place in the order of destruction within the scope of that
function. The return value object needs to be initialized before the
lifetime of any local variables end. And other local variables should
be able to access any objects that are after themselves in that order
of destruction.
So doing what you suggest requires changing the order of
initialization and destruction of local variables. That's not
unreasonable. However, if you want to do that, it *must* happen
through explicit syntax.
This is why understanding the philosophy behind something like
guaranteed NRVO is so important. It exposes the issues of going from
lvalue to prvalue back to lvalue and the myriad of complexities around
it. You can't just gloss over it; it must be central to any such
proposal.
Received on 2024-07-15 15:33:11