Date: Mon, 11 Oct 2021 15:39:08 -0700
On Mon, Oct 11, 2021 at 2:47 PM Daveed Vandevoorde <daveed_at_[hidden]> wrote:
> On Oct 11, 2021, at 4:06 PM, Richard Smith via Core <core_at_[hidden]>
> wrote:
>
> +SG12 for UB / object model question.
>
> On Sun, Oct 10, 2021 at 4:27 AM Andrey Erokhin <language.lawyer_at_[hidden]>
> wrote:
>
>> [intro.object]/2 says:
>> > If an object is created in storage associated with a member subobject
>> or array element /e/ (which may or may not be within its lifetime), the
>> created object is a subobject of /e/'s containing object if:
>> > — the lifetime of /e/'s containing object has begun and not ended, and
>> > — the storage for the new object exactly overlays the storage location
>> associated with /e/, and
>> > — the new object is of the same type as /e/ (ignoring cv-qualification).
>>
>> Now, lets look at
>>
>> struct S
>> {
>> std::byte m;
>>
>
> Let's imagine there's some more stuff here so sizeof(S) is larger:
>
> char padding[123];
> int align_to_int;
>
>
>> };
>>
>> std::byte buf[sizeof(S)];
>>
>> auto p = new (buf) S {}; //1
>> new (buf) std::byte{}; //2
>>
>
> If we continue this example:
>
> int *p = new (buf + sizeof(int)) int;
>
> ... then we know at this point the lifetime of S has ended because its
> storage was reused. So if the object created at //2 was a subobject of the
> S object created at //1, then that object is in a bad state -- it's a
> subobject of an object whose lifetime has ended. That sounds problematic --
> at least in principle -- for things that look like variant<S, std::byte>.
>
> So I think either we want to say that the newly-created object at //2 is a
> subobject of two different objects or we want to say that we use angelic
> nondeterminism here (//2 either creates a subobject of S, replacing s.m, or
> creates a subobject of buf, replacing buf[0], depending on how the program
> continues). Given that we already have angelic nondeterminsm in the object
> model and don't otherwise have cases where the subobject structure is not a
> tree, I think the angelic nondeterminism model would likely be the better
> option.
>
>
> +1, but can we call it (“angelic nondeterminism”) something else ;-) ?
> Maybe “retroacted nondeterminism”?
>
Sure, we can call it something else, and probably should. We need to
distinguish between the "we pick the option that makes your program work"
nondeterminism (here) and "we pick the option that breaks your program"
nondeterminism (UB, formally), though, so having two parallel but opposing
terms seems nice. Maybe optimistic nondeterminism and pessimistic
nondeterminism?
> I haven’t thought about the consequences for constexpr evaluation yet, but
> it’s not giving me the warm-and-fuzzies...
>
We probably should not permit this in constant evaluation, like we said we
wouldn't for other kinds of optimistic nondeterminism.
> Daveed
>
>
>
> That and, I don't think it's desirable to allow a newly-created object to
> be used as two different subobjects -- the program should not be engaging
> in such object identity punning.
>
> A few words about //1: well, an object of std::byte type which is a
>> subobject corresponding to S::m NSDM is "created" there, but I don't think
>> [intro.object]/2 cares about it. So lets ignore line //1 here.
>>
>> In //2, however, the newly-created complete object of std::byte type
>> becomes both array element of buf and member subobject of *p, according to
>> [intro.object]/2. And I don't think this is what we want.
>>
>> What if we rewrite the rule as follows:
>> > If an object is created in storage associated with a member subobject
>> or array element /e/ (which may or may not be within its lifetime), the
>> created object is a subobject of /e/'s containing object <ins>/c/</ins> if:
>> > — the lifetime of <del>/e/'s containing object</del><ins>/c/</ins> has
>> begun and not ended, and
>> > — the storage for the new object exactly overlays the storage location
>> associated with /e/, and
>> > — the new object is of the same type as /e/ (ignoring
>> cv-qualification)<del>.</del><ins>, and</ins>
>> > <ins>— there is no member subobject or array element /e1/ and its
>> containing object /c1/ satisfying these constraints nested within /c/.</ins>
>>
>> Then, the newly-created complete object of std::byte type will only
>> become a member subobject of *p.
>>
>
> (Note from Andrey for SG12's benefit: *In the previous message, the word
> "complete" should be completely ignored.)
> _______________________________________________
> Core mailing list
> Core_at_[hidden]
> Subscription: https://lists.isocpp.org/mailman/listinfo.cgi/core
> Link to this post: http://lists.isocpp.org/core/2021/10/11627.php
>
>
>
> On Oct 11, 2021, at 4:06 PM, Richard Smith via Core <core_at_[hidden]>
> wrote:
>
> +SG12 for UB / object model question.
>
> On Sun, Oct 10, 2021 at 4:27 AM Andrey Erokhin <language.lawyer_at_[hidden]>
> wrote:
>
>> [intro.object]/2 says:
>> > If an object is created in storage associated with a member subobject
>> or array element /e/ (which may or may not be within its lifetime), the
>> created object is a subobject of /e/'s containing object if:
>> > — the lifetime of /e/'s containing object has begun and not ended, and
>> > — the storage for the new object exactly overlays the storage location
>> associated with /e/, and
>> > — the new object is of the same type as /e/ (ignoring cv-qualification).
>>
>> Now, lets look at
>>
>> struct S
>> {
>> std::byte m;
>>
>
> Let's imagine there's some more stuff here so sizeof(S) is larger:
>
> char padding[123];
> int align_to_int;
>
>
>> };
>>
>> std::byte buf[sizeof(S)];
>>
>> auto p = new (buf) S {}; //1
>> new (buf) std::byte{}; //2
>>
>
> If we continue this example:
>
> int *p = new (buf + sizeof(int)) int;
>
> ... then we know at this point the lifetime of S has ended because its
> storage was reused. So if the object created at //2 was a subobject of the
> S object created at //1, then that object is in a bad state -- it's a
> subobject of an object whose lifetime has ended. That sounds problematic --
> at least in principle -- for things that look like variant<S, std::byte>.
>
> So I think either we want to say that the newly-created object at //2 is a
> subobject of two different objects or we want to say that we use angelic
> nondeterminism here (//2 either creates a subobject of S, replacing s.m, or
> creates a subobject of buf, replacing buf[0], depending on how the program
> continues). Given that we already have angelic nondeterminsm in the object
> model and don't otherwise have cases where the subobject structure is not a
> tree, I think the angelic nondeterminism model would likely be the better
> option.
>
>
> +1, but can we call it (“angelic nondeterminism”) something else ;-) ?
> Maybe “retroacted nondeterminism”?
>
Sure, we can call it something else, and probably should. We need to
distinguish between the "we pick the option that makes your program work"
nondeterminism (here) and "we pick the option that breaks your program"
nondeterminism (UB, formally), though, so having two parallel but opposing
terms seems nice. Maybe optimistic nondeterminism and pessimistic
nondeterminism?
> I haven’t thought about the consequences for constexpr evaluation yet, but
> it’s not giving me the warm-and-fuzzies...
>
We probably should not permit this in constant evaluation, like we said we
wouldn't for other kinds of optimistic nondeterminism.
> Daveed
>
>
>
> That and, I don't think it's desirable to allow a newly-created object to
> be used as two different subobjects -- the program should not be engaging
> in such object identity punning.
>
> A few words about //1: well, an object of std::byte type which is a
>> subobject corresponding to S::m NSDM is "created" there, but I don't think
>> [intro.object]/2 cares about it. So lets ignore line //1 here.
>>
>> In //2, however, the newly-created complete object of std::byte type
>> becomes both array element of buf and member subobject of *p, according to
>> [intro.object]/2. And I don't think this is what we want.
>>
>> What if we rewrite the rule as follows:
>> > If an object is created in storage associated with a member subobject
>> or array element /e/ (which may or may not be within its lifetime), the
>> created object is a subobject of /e/'s containing object <ins>/c/</ins> if:
>> > — the lifetime of <del>/e/'s containing object</del><ins>/c/</ins> has
>> begun and not ended, and
>> > — the storage for the new object exactly overlays the storage location
>> associated with /e/, and
>> > — the new object is of the same type as /e/ (ignoring
>> cv-qualification)<del>.</del><ins>, and</ins>
>> > <ins>— there is no member subobject or array element /e1/ and its
>> containing object /c1/ satisfying these constraints nested within /c/.</ins>
>>
>> Then, the newly-created complete object of std::byte type will only
>> become a member subobject of *p.
>>
>
> (Note from Andrey for SG12's benefit: *In the previous message, the word
> "complete" should be completely ignored.)
> _______________________________________________
> Core mailing list
> Core_at_[hidden]
> Subscription: https://lists.isocpp.org/mailman/listinfo.cgi/core
> Link to this post: http://lists.isocpp.org/core/2021/10/11627.php
>
>
>
Received on 2021-10-11 17:39:22