Discussion:
Can an object be nested within another of the same type?
(too old to reply)
Chris Hallock
2017-09-12 03:58:15 UTC
Permalink
For example, does the *new-expression* in the program below
1) end the lifetime of x (by reusing its storage) and create another object
in its place, or
2) create a new A object nested within x?

#include <new>

struct A { unsigned char buf[1]; };
static_assert(sizeof(A) == 1); // A can fit within A::buf

int main()
{
A x{};
new (x.buf) A{};
}
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Thiago Macieira
2017-09-12 12:46:49 UTC
Permalink
Post by Chris Hallock
For example, does the *new-expression* in the program below
1) end the lifetime of x (by reusing its storage) and create another object
in its place, or
2) create a new A object nested within x?
It's not nested, it's actually the same object. The only way an object is big
enough to contain an object of the same size is if that sub-object occupies
the entire object.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Richard Smith
2017-09-12 16:54:37 UTC
Permalink
Post by Chris Hallock
For example, does the *new-expression* in the program below
1) end the lifetime of x (by reusing its storage) and create another
object in its place, or
2) create a new A object nested within x?
Under [intro.object]p3, the original object provides storage for the new
object. Under [basic.life]p8, the name of the original object is rebound to
the new object. But we did not meet the requirements of [basic.life]p5, so
the original object's lifetime does not end. I think you end up with a
"dangling" outer A object that can no longer be named, providing storage
for an inner A object that is now named x.

In terms of the observable behavior of the program, I think this is
equivalent to replacing the original object with a new object.
Post by Chris Hallock
#include <new>
struct A { unsigned char buf[1]; };
static_assert(sizeof(A) == 1); // A can fit within A::buf
int main()
{
A x{};
new (x.buf) A{};
}
--
---
You received this message because you are subscribed to the Google Groups
"ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an
Visit this group at https://groups.google.com/a/isocpp.org/group/std-
discussion/.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Chris Hallock
2017-09-12 18:00:48 UTC
Permalink
Post by Richard Smith
Post by Chris Hallock
For example, does the *new-expression* in the program below
1) end the lifetime of x (by reusing its storage) and create another
object in its place, or
2) create a new A object nested within x?
Under [intro.object]p3, the original object provides storage for the new
object. Under [basic.life]p8, the name of the original object is rebound to
the new object. But we did not meet the requirements of [basic.life]p5, so
the original object's lifetime does not end. I think you end up with a
"dangling" outer A object that can no longer be named, providing storage
for an inner A object that is now named x.
In terms of the observable behavior of the program, I think this is
equivalent to replacing the original object with a new object.
(Messed up my previous reply, trying again...)

But if the previous object didn't end, then rebinding doesn't happen —
[basic.life]/8 starts "If, after the lifetime of an object has ended...".
What's unclear is whether the new-expression constitutes a re-use of x's
storage or of x.buf's storage.

If the new-expression were new (&x) A{} instead of new (x.buf) A{}, does
that change anything? I can't find any Standardese that clearly
differentiates the behavior of these two expressions.

If nesting does indeed occur, that would seem to violate the assumed
principle of the C++ object model that no two objects of the same type can
exist simultaneously at the same address (besides unsigned char or
std::byte, maybe). std::launder() in particular appears to make that
assumption.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Richard Smith
2017-09-12 19:16:57 UTC
Permalink
Post by Chris Hallock
Post by Richard Smith
Post by Chris Hallock
For example, does the *new-expression* in the program below
1) end the lifetime of x (by reusing its storage) and create another
object in its place, or
2) create a new A object nested within x?
Under [intro.object]p3, the original object provides storage for the new
object. Under [basic.life]p8, the name of the original object is rebound to
the new object. But we did not meet the requirements of [basic.life]p5, so
the original object's lifetime does not end. I think you end up with a
"dangling" outer A object that can no longer be named, providing storage
for an inner A object that is now named x.
In terms of the observable behavior of the program, I think this is
equivalent to replacing the original object with a new object.
(Messed up my previous reply, trying again...)
But if the previous object didn't end, then rebinding doesn't happen —
[basic.life]/8 starts "If, after the lifetime of an object has ended...".
Good point. I think most of the above still applies: the lifetime rule in
[basic.life]/1.4 requires that we first consider whether [intro.object]/3
applies. It does, and the buffer provides storage for the new object, so
the lifetime of 'x' does not end. So 'x' is not rebound to the new object,
and its buffer holds another X object.
Post by Chris Hallock
What's unclear is whether the new-expression constitutes a re-use of x's
storage or of x.buf's storage.
If the new-expression were new (&x) A{} instead of new (x.buf) A{}, does
that change anything? I can't find any Standardese that clearly
differentiates the behavior of these two expressions.
I agree. The wording in [intro.object] and [basic.life] concern themselves
with the storage address represented by a pointer, not the object to which
it points.

If nesting does indeed occur, that would seem to violate the assumed
Post by Chris Hallock
principle of the C++ object model that no two objects of the same type can
exist simultaneously at the same address (besides unsigned char or
std::byte, maybe).
That holds even for unsigned char / std::byte: if you placement new, say, a
"struct A { unsigned char c; };" into an "unsigned char buffer[1];", the
lifetime of the unsigned char object buffer[0] ends even though the
lifetime of the buffer array itself does not.

Perhaps Thiago's response is best: [intro.object]p3 should not apply if the
array is nested within an object of the new object's type. The containing
object must then necessarily be within its lifetime, have exactly
overlapping storage, and contain no const or reference members, implying we
instead reach either [intro.object]p2 (if that object is a subobject) or
[basic.life]p8 (otherwise).

Would you like to file this as a defect report?
Post by Chris Hallock
std::launder() in particular appears to make that assumption.
--
---
You received this message because you are subscribed to the Google Groups
"ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an
Visit this group at https://groups.google.com/a/isocpp.org/group/std-
discussion/.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Chris Hallock
2017-09-13 16:14:56 UTC
Permalink
(Google Groups must be hungry, because it keeps eating my posts...)
Post by Richard Smith
Post by Chris Hallock
Post by Richard Smith
Post by Chris Hallock
For example, does the *new-expression* in the program below
1) end the lifetime of x (by reusing its storage) and create another
object in its place, or
2) create a new A object nested within x?
Under [intro.object]p3, the original object provides storage for the new
object. Under [basic.life]p8, the name of the original object is rebound to
the new object. But we did not meet the requirements of [basic.life]p5, so
the original object's lifetime does not end. I think you end up with a
"dangling" outer A object that can no longer be named, providing storage
for an inner A object that is now named x.
In terms of the observable behavior of the program, I think this is
equivalent to replacing the original object with a new object.
(Messed up my previous reply, trying again...)
But if the previous object didn't end, then rebinding doesn't happen —
[basic.life]/8 starts "If, after the lifetime of an object has ended...".
Good point. I think most of the above still applies: the lifetime rule in
[basic.life]/1.4 requires that we first consider whether [intro.object]/3
applies. It does, and the buffer provides storage for the new object, so
the lifetime of 'x' does not end. So 'x' is not rebound to the new object,
and its buffer holds another X object.
Post by Chris Hallock
What's unclear is whether the new-expression constitutes a re-use of x's
storage or of x.buf's storage.
If the new-expression were new (&x) A{} instead of new (x.buf) A{}, does
that change anything? I can't find any Standardese that clearly
differentiates the behavior of these two expressions.
I agree. The wording in [intro.object] and [basic.life] concern themselves
with the storage address represented by a pointer, not the object to which
it points.
If nesting does indeed occur, that would seem to violate the assumed
Post by Chris Hallock
principle of the C++ object model that no two objects of the same type can
exist simultaneously at the same address (besides unsigned char or
std::byte, maybe).
That holds even for unsigned char / std::byte: if you placement new, say,
a "struct A { unsigned char c; };" into an "unsigned char buffer[1];", the
lifetime of the unsigned char object buffer[0] ends even though the
lifetime of the buffer array itself does not.
Perhaps Thiago's response is best: [intro.object]p3 should not apply if
the array is nested within an object of the new object's type. The
containing object must then necessarily be within its lifetime, have
exactly overlapping storage, and contain no const or reference members,
implying we instead reach either [intro.object]p2 (if that object is a
subobject) or [basic.life]p8 (otherwise).
I agree that this should be prohibited. Would it suffice to add another
constraint to [intro.object]/3? Something like "the created object would
have an address different from any existing object of the same type
(ignoring cv qualification)". Then new (x.buf) A{} would replace x instead
of nest within, because we prohibited nesting.


Would you like to file this as a defect report?
I'd be happy to submit an issue report, if you're inviting me to (?).
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Richard Smith
2017-09-13 19:35:03 UTC
Permalink
Post by Chris Hallock
(Google Groups must be hungry, because it keeps eating my posts...)
Post by Richard Smith
Post by Chris Hallock
Post by Richard Smith
Post by Chris Hallock
For example, does the *new-expression* in the program below
1) end the lifetime of x (by reusing its storage) and create another
object in its place, or
2) create a new A object nested within x?
Under [intro.object]p3, the original object provides storage for the
new object. Under [basic.life]p8, the name of the original object is
rebound to the new object. But we did not meet the requirements of
[basic.life]p5, so the original object's lifetime does not end. I think you
end up with a "dangling" outer A object that can no longer be named,
providing storage for an inner A object that is now named x.
In terms of the observable behavior of the program, I think this is
equivalent to replacing the original object with a new object.
(Messed up my previous reply, trying again...)
But if the previous object didn't end, then rebinding doesn't happen —
[basic.life]/8 starts "If, after the lifetime of an object has ended...".
Good point. I think most of the above still applies: the lifetime rule in
[basic.life]/1.4 requires that we first consider whether [intro.object]/3
applies. It does, and the buffer provides storage for the new object, so
the lifetime of 'x' does not end. So 'x' is not rebound to the new object,
and its buffer holds another X object.
Post by Chris Hallock
What's unclear is whether the new-expression constitutes a re-use of x's
storage or of x.buf's storage.
If the new-expression were new (&x) A{} instead of new (x.buf) A{},
does that change anything? I can't find any Standardese that clearly
differentiates the behavior of these two expressions.
I agree. The wording in [intro.object] and [basic.life] concern
themselves with the storage address represented by a pointer, not the
object to which it points.
If nesting does indeed occur, that would seem to violate the assumed
Post by Chris Hallock
principle of the C++ object model that no two objects of the same type can
exist simultaneously at the same address (besides unsigned char or
std::byte, maybe).
That holds even for unsigned char / std::byte: if you placement new, say,
a "struct A { unsigned char c; };" into an "unsigned char buffer[1];", the
lifetime of the unsigned char object buffer[0] ends even though the
lifetime of the buffer array itself does not.
Perhaps Thiago's response is best: [intro.object]p3 should not apply if
the array is nested within an object of the new object's type. The
containing object must then necessarily be within its lifetime, have
exactly overlapping storage, and contain no const or reference members,
implying we instead reach either [intro.object]p2 (if that object is a
subobject) or [basic.life]p8 (otherwise).
I agree that this should be prohibited. Would it suffice to add another
constraint to [intro.object]/3? Something like "the created object would
have an address different from any existing object of the same type
(ignoring cv qualification)". Then new (x.buf) A{} would replace
x instead of nest within, because we prohibited nesting.
I think it'd be better to say something like "the array is not nested
within an object of the same type (ignoring cv qualification) that is
within its lifetime", because I think your rule creates a circularity: if
the A object is nested within the array, then it violates that rule and is
not nested within. Otherwise, it ends the lifetime of the enclosing A
object and no longer violates that rule, which means it *is* nested within
the array and so does not end the lifetime of the enclosing A object.
Post by Chris Hallock
Would you like to file this as a defect report?
I'd be happy to submit an issue report, if you're inviting me to (?).
I am :)
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Chris Hallock
2017-09-13 22:46:29 UTC
Permalink
Post by Richard Smith
Post by Chris Hallock
(Google Groups must be hungry, because it keeps eating my posts...)
Post by Richard Smith
Post by Chris Hallock
Post by Richard Smith
Post by Chris Hallock
For example, does the *new-expression* in the program below
1) end the lifetime of x (by reusing its storage) and create another
object in its place, or
2) create a new A object nested within x?
Under [intro.object]p3, the original object provides storage for the
new object. Under [basic.life]p8, the name of the original object is
rebound to the new object. But we did not meet the requirements of
[basic.life]p5, so the original object's lifetime does not end. I think you
end up with a "dangling" outer A object that can no longer be named,
providing storage for an inner A object that is now named x.
In terms of the observable behavior of the program, I think this is
equivalent to replacing the original object with a new object.
(Messed up my previous reply, trying again...)
But if the previous object didn't end, then rebinding doesn't happen —
[basic.life]/8 starts "If, after the lifetime of an object has ended...".
Good point. I think most of the above still applies: the lifetime rule
in [basic.life]/1.4 requires that we first consider whether
[intro.object]/3 applies. It does, and the buffer provides storage for the
new object, so the lifetime of 'x' does not end. So 'x' is not rebound to
the new object, and its buffer holds another X object.
Post by Chris Hallock
What's unclear is whether the new-expression constitutes a re-use of
x's storage or of x.buf's storage.
If the new-expression were new (&x) A{} instead of new (x.buf) A{},
does that change anything? I can't find any Standardese that clearly
differentiates the behavior of these two expressions.
I agree. The wording in [intro.object] and [basic.life] concern
themselves with the storage address represented by a pointer, not the
object to which it points.
If nesting does indeed occur, that would seem to violate the assumed
Post by Chris Hallock
principle of the C++ object model that no two objects of the same type can
exist simultaneously at the same address (besides unsigned char or
std::byte, maybe).
That holds even for unsigned char / std::byte: if you placement new,
say, a "struct A { unsigned char c; };" into an "unsigned char buffer[1];",
the lifetime of the unsigned char object buffer[0] ends even though the
lifetime of the buffer array itself does not.
Perhaps Thiago's response is best: [intro.object]p3 should not apply if
the array is nested within an object of the new object's type. The
containing object must then necessarily be within its lifetime, have
exactly overlapping storage, and contain no const or reference members,
implying we instead reach either [intro.object]p2 (if that object is a
subobject) or [basic.life]p8 (otherwise).
I agree that this should be prohibited. Would it suffice to add another
constraint to [intro.object]/3? Something like "the created object would
have an address different from any existing object of the same type
(ignoring cv qualification)". Then new (x.buf) A{} would replace
x instead of nest within, because we prohibited nesting.
I think it'd be better to say something like "the array is not nested
within an object of the same type (ignoring cv qualification) that is
within its lifetime", because I think your rule creates a circularity: if
the A object is nested within the array, then it violates that rule and is
not nested within. Otherwise, it ends the lifetime of the enclosing A
object and no longer violates that rule, which means it *is* nested within
the array and so does not end the lifetime of the enclosing A object.
Ah, that makes sense.


Would you like to file this as a defect report?
Post by Richard Smith
Post by Chris Hallock
I'd be happy to submit an issue report, if you're inviting me to (?).
I am :)
I've got that ball rolling. Thanks for your help!
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Language Lawyer
2018-09-16 18:34:52 UTC
Permalink
Post by Richard Smith
Post by Chris Hallock
(Google Groups must be hungry, because it keeps eating my posts...)
Post by Richard Smith
Post by Chris Hallock
Post by Richard Smith
Post by Chris Hallock
For example, does the *new-expression* in the program below
1) end the lifetime of x (by reusing its storage) and create another
object in its place, or
2) create a new A object nested within x?
Under [intro.object]p3, the original object provides storage for the
new object. Under [basic.life]p8, the name of the original object is
rebound to the new object. But we did not meet the requirements of
[basic.life]p5, so the original object's lifetime does not end. I think you
end up with a "dangling" outer A object that can no longer be named,
providing storage for an inner A object that is now named x.
In terms of the observable behavior of the program, I think this is
equivalent to replacing the original object with a new object.
(Messed up my previous reply, trying again...)
But if the previous object didn't end, then rebinding doesn't happen —
[basic.life]/8 starts "If, after the lifetime of an object has ended...".
Good point. I think most of the above still applies: the lifetime rule
in [basic.life]/1.4 requires that we first consider whether
[intro.object]/3 applies. It does, and the buffer provides storage for the
new object, so the lifetime of 'x' does not end. So 'x' is not rebound to
the new object, and its buffer holds another X object.
Post by Chris Hallock
What's unclear is whether the new-expression constitutes a re-use of
x's storage or of x.buf's storage.
If the new-expression were new (&x) A{} instead of new (x.buf) A{},
does that change anything? I can't find any Standardese that clearly
differentiates the behavior of these two expressions.
I agree. The wording in [intro.object] and [basic.life] concern
themselves with the storage address represented by a pointer, not the
object to which it points.
If nesting does indeed occur, that would seem to violate the assumed
Post by Chris Hallock
principle of the C++ object model that no two objects of the same type can
exist simultaneously at the same address (besides unsigned char or
std::byte, maybe).
That holds even for unsigned char / std::byte: if you placement new,
say, a "struct A { unsigned char c; };" into an "unsigned char buffer[1];",
the lifetime of the unsigned char object buffer[0] ends even though the
lifetime of the buffer array itself does not.
Perhaps Thiago's response is best: [intro.object]p3 should not apply if
the array is nested within an object of the new object's type. The
containing object must then necessarily be within its lifetime, have
exactly overlapping storage, and contain no const or reference members,
implying we instead reach either [intro.object]p2 (if that object is a
subobject) or [basic.life]p8 (otherwise).
I agree that this should be prohibited. Would it suffice to add another
constraint to [intro.object]/3? Something like "the created object would
have an address different from any existing object of the same type
(ignoring cv qualification)". Then new (x.buf) A{} would replace
x instead of nest within, because we prohibited nesting.
I think it'd be better to say something like "the array is not nested
within an object of the same type (ignoring cv qualification) that is
within its lifetime"
Your suggestion is also not perfect, because it does not prevent an
`unsigned char` or `std::byte` array of size `N` to be nested within the
array of the same size and type arbitrary number of times without ending
the lifetime of the original array and all the arrays created before.

One thing that could prevent this is a non-negative unspecified array
overhead. But, for example, in Itanium C++ ABI the overhead is zero for
trivially destructible types
(https://itanium-cxx-abi.github.io/cxx-abi/abi.html#array-cookies (it says
it is zero "if the array element type T has a trivial destructor (12.4
[class.dtor])" but in fact it is zero for non-class types too)).
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Language Lawyer
2018-09-16 18:45:21 UTC
Permalink
Post by Language Lawyer
Post by Richard Smith
Post by Chris Hallock
(Google Groups must be hungry, because it keeps eating my posts...)
Post by Richard Smith
Post by Chris Hallock
Post by Richard Smith
Post by Chris Hallock
For example, does the *new-expression* in the program below
1) end the lifetime of x (by reusing its storage) and create another
object in its place, or
2) create a new A object nested within x?
Under [intro.object]p3, the original object provides storage for the
new object. Under [basic.life]p8, the name of the original object is
rebound to the new object. But we did not meet the requirements of
[basic.life]p5, so the original object's lifetime does not end. I think you
end up with a "dangling" outer A object that can no longer be named,
providing storage for an inner A object that is now named x.
In terms of the observable behavior of the program, I think this is
equivalent to replacing the original object with a new object.
(Messed up my previous reply, trying again...)
But if the previous object didn't end, then rebinding doesn't happen —
[basic.life]/8 starts "If, after the lifetime of an object has ended...".
Good point. I think most of the above still applies: the lifetime rule
in [basic.life]/1.4 requires that we first consider whether
[intro.object]/3 applies. It does, and the buffer provides storage for the
new object, so the lifetime of 'x' does not end. So 'x' is not rebound to
the new object, and its buffer holds another X object.
Post by Chris Hallock
What's unclear is whether the new-expression constitutes a re-use of
x's storage or of x.buf's storage.
If the new-expression were new (&x) A{} instead of new (x.buf) A{},
does that change anything? I can't find any Standardese that clearly
differentiates the behavior of these two expressions.
I agree. The wording in [intro.object] and [basic.life] concern
themselves with the storage address represented by a pointer, not the
object to which it points.
If nesting does indeed occur, that would seem to violate the assumed
Post by Chris Hallock
principle of the C++ object model that no two objects of the same type can
exist simultaneously at the same address (besides unsigned char or
std::byte, maybe).
That holds even for unsigned char / std::byte: if you placement new,
say, a "struct A { unsigned char c; };" into an "unsigned char buffer[1];",
the lifetime of the unsigned char object buffer[0] ends even though the
lifetime of the buffer array itself does not.
Perhaps Thiago's response is best: [intro.object]p3 should not apply if
the array is nested within an object of the new object's type. The
containing object must then necessarily be within its lifetime, have
exactly overlapping storage, and contain no const or reference members,
implying we instead reach either [intro.object]p2 (if that object is a
subobject) or [basic.life]p8 (otherwise).
I agree that this should be prohibited. Would it suffice to add another
constraint to [intro.object]/3? Something like "the created object would
have an address different from any existing object of the same type
(ignoring cv qualification)". Then new (x.buf) A{} would replace
x instead of nest within, because we prohibited nesting.
I think it'd be better to say something like "the array is not nested
within an object of the same type (ignoring cv qualification) that is
within its lifetime"
Your suggestion is also not perfect, because it does not prevent an
`unsigned char` or `std::byte` array of size `N` to be nested within the
array of the same size and type arbitrary number of times without ending
the lifetime of the original array and all the arrays created before.
Oops. I was wrong.

You may nest an `unsigned char` or `std::byte` array inside an array of the same type only *once*.
But then the nested array won't provide storage for further arrays of the same size and type because it is nested within an object of the same type.

But still one can haz two objects with the same type and overlapping lifetimes at the same address.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Chris Hallock
2017-09-13 15:37:53 UTC
Permalink
(Reposting because Google Groups deleted my reply for some reason)
Post by Richard Smith
Post by Chris Hallock
Post by Richard Smith
Post by Chris Hallock
For example, does the *new-expression* in the program below
1) end the lifetime of x (by reusing its storage) and create another
object in its place, or
2) create a new A object nested within x?
Under [intro.object]p3, the original object provides storage for the new
object. Under [basic.life]p8, the name of the original object is rebound to
the new object. But we did not meet the requirements of [basic.life]p5, so
the original object's lifetime does not end. I think you end up with a
"dangling" outer A object that can no longer be named, providing storage
for an inner A object that is now named x.
In terms of the observable behavior of the program, I think this is
equivalent to replacing the original object with a new object.
(Messed up my previous reply, trying again...)
But if the previous object didn't end, then rebinding doesn't happen —
[basic.life]/8 starts "If, after the lifetime of an object has ended...".
Good point. I think most of the above still applies: the lifetime rule in
[basic.life]/1.4 requires that we first consider whether [intro.object]/3
applies. It does, and the buffer provides storage for the new object, so
the lifetime of 'x' does not end. So 'x' is not rebound to the new object,
and its buffer holds another X object.
Post by Chris Hallock
What's unclear is whether the new-expression constitutes a re-use of x's
storage or of x.buf's storage.
If the new-expression were new (&x) A{} instead of new (x.buf) A{}, does
that change anything? I can't find any Standardese that clearly
differentiates the behavior of these two expressions.
I agree. The wording in [intro.object] and [basic.life] concern themselves
with the storage address represented by a pointer, not the object to which
it points.
If nesting does indeed occur, that would seem to violate the assumed
Post by Chris Hallock
principle of the C++ object model that no two objects of the same type can
exist simultaneously at the same address (besides unsigned char or
std::byte, maybe).
That holds even for unsigned char / std::byte: if you placement new, say,
a "struct A { unsigned char c; };" into an "unsigned char buffer[1];", the
lifetime of the unsigned char object buffer[0] ends even though the
lifetime of the buffer array itself does not.
Perhaps Thiago's response is best: [intro.object]p3 should not apply if
the array is nested within an object of the new object's type. The
containing object must then necessarily be within its lifetime, have
exactly overlapping storage, and contain no const or reference members,
implying we instead reach either [intro.object]p2 (if that object is a
subobject) or [basic.life]p8 (otherwise).
I agree that this should be prohibited. Would it suffice to add another
constraint to [intro.object]/3? Something like "the created object would
have an address different from any existing object of the same type
(ignoring cv qualification)". Then new (x.buf) A{} would replace x instead
of nesting, because it isn't allowed to nest.
Post by Richard Smith
Would you like to file this as a defect report?
I'd be happy to submit an issue report, if you're inviting me to (?).
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Chris Hallock
2017-09-13 08:21:16 UTC
Permalink
Post by Richard Smith
Post by Chris Hallock
Post by Richard Smith
Post by Chris Hallock
For example, does the *new-expression* in the program below
1) end the lifetime of x (by reusing its storage) and create another
object in its place, or
2) create a new A object nested within x?
Under [intro.object]p3, the original object provides storage for the new
object. Under [basic.life]p8, the name of the original object is rebound to
the new object. But we did not meet the requirements of [basic.life]p5, so
the original object's lifetime does not end. I think you end up with a
"dangling" outer A object that can no longer be named, providing storage
for an inner A object that is now named x.
In terms of the observable behavior of the program, I think this is
equivalent to replacing the original object with a new object.
(Messed up my previous reply, trying again...)
But if the previous object didn't end, then rebinding doesn't happen —
[basic.life]/8 starts "If, after the lifetime of an object has ended...".
Good point. I think most of the above still applies: the lifetime rule in
[basic.life]/1.4 requires that we first consider whether [intro.object]/3
applies. It does, and the buffer provides storage for the new object, so
the lifetime of 'x' does not end. So 'x' is not rebound to the new object,
and its buffer holds another X object.
Post by Chris Hallock
What's unclear is whether the new-expression constitutes a re-use of x's
storage or of x.buf's storage.
If the new-expression were new (&x) A{} instead of new (x.buf) A{}, does
that change anything? I can't find any Standardese that clearly
differentiates the behavior of these two expressions.
I agree. The wording in [intro.object] and [basic.life] concern themselves
with the storage address represented by a pointer, not the object to which
it points.
If nesting does indeed occur, that would seem to violate the assumed
Post by Chris Hallock
principle of the C++ object model that no two objects of the same type can
exist simultaneously at the same address (besides unsigned char or
std::byte, maybe).
That holds even for unsigned char / std::byte: if you placement new, say,
a "struct A { unsigned char c; };" into an "unsigned char buffer[1];", the
lifetime of the unsigned char object buffer[0] ends even though the
lifetime of the buffer array itself does not.
Perhaps Thiago's response is best: [intro.object]p3 should not apply if
the array is nested within an object of the new object's type. The
containing object must then necessarily be within its lifetime, have
exactly overlapping storage, and contain no const or reference members,
implying we instead reach either [intro.object]p2 (if that object is a
subobject) or [basic.life]p8 (otherwise).
I agree that we should prohibit this. Wouldn't it suffice to add a new
restriction to [intro.object]/3? Something like "— the created object does
not have the same address as another object with a similar ([conv.qual])
type." This would cause new (x.buf) A{} to replace the x object instead of
nesting into it, since it can't nest.

Would you like to file this as a defect report?
I'd be happy to submit an issue report, if that's what you mean (?).
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Chris Hallock
2017-09-12 17:43:40 UTC
Permalink
Post by Richard Smith
Post by Chris Hallock
For example, does the *new-expression* in the program below
1) end the lifetime of x (by reusing its storage) and create another
object in its place, or
2) create a new A object nested within x?
Under [intro.object]p3, the original object provides storage for the new
object. Under [basic.life]p8, the name of the original object is rebound to
the new object. But we did not meet the requirements of [basic.life]p5, so
the original object's lifetime does not end. I think you end up with a
"dangling" outer A object that can no longer be named, providing storage
for an inner A object that is now named x.
In terms of the observable behavior of the program, I think this is
equivalent to replacing the original object with a new object.
But if the original object didn't end, no names are rebound —
[basic.life]/8 starts with "If, after the lifetime of an object has
ended...". What is unclear is whether the new-expression constitutes a
re-use of x's storage or of x.buf's storage, since they are the same
storage.

Further questions:

If the *new-expression* were new (&x) A{} instead of new (x.buf) A{}, does
that change anything?

If nesting occurs in my example, which A object would std::launder(&x)
point to? What about std::launder(reinterpret_cast<A*>(x.buf))? The wording
for std::launder() seems to assume that there can be at most one object of
a given type at a given address at a given time.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Continue reading on narkive:
Loading...