Date: Mon, 11 Oct 2021 20:53:05 +0000
Do we have concrete examples of sound programming techniques that would benefit from having a subobject be part of two distinct complete objects?
The only close example (in fact it is not a real close analogy) I have is virtual base class subobjects – it is not a real analogy as such subobjects are practically implemented via indirections (which is not the case here).
-- Gaby
From: Core <core-bounces_at_[hidden]> On Behalf Of Richard Smith via Core
Sent: Monday, October 11, 2021 1:07 PM
To: Andrey Erokhin <language.lawyer_at_[hidden]>; sg12_at_[hidden]
Cc: Richard Smith <richardsmith_at_[hidden]>; C++ Core Language Working Group <core_at_[hidden]>
Subject: Re: [isocpp-core] Are we OK with objects which are subobjects of 2 (or more) objects at the same time?
+SG12 for UB / object model question.
On Sun, Oct 10, 2021 at 4:27 AM Andrey Erokhin <language.lawyer_at_[hidden]<mailto: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. 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.)
The only close example (in fact it is not a real close analogy) I have is virtual base class subobjects – it is not a real analogy as such subobjects are practically implemented via indirections (which is not the case here).
-- Gaby
From: Core <core-bounces_at_[hidden]> On Behalf Of Richard Smith via Core
Sent: Monday, October 11, 2021 1:07 PM
To: Andrey Erokhin <language.lawyer_at_[hidden]>; sg12_at_[hidden]
Cc: Richard Smith <richardsmith_at_[hidden]>; C++ Core Language Working Group <core_at_[hidden]>
Subject: Re: [isocpp-core] Are we OK with objects which are subobjects of 2 (or more) objects at the same time?
+SG12 for UB / object model question.
On Sun, Oct 10, 2021 at 4:27 AM Andrey Erokhin <language.lawyer_at_[hidden]<mailto: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. 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.)
Received on 2021-10-11 15:53:10