This is a follow-up to my previous question where I seem to have made the problem more involved than I had originally intended. (See discussions in question and answer comme
If a
is destroyed (whether by delete
or by falling out of scope), then t.~T()
is called, which is UB if t
isn't actually a T
(by not calling destruct
).
This doesn't apply if
T
is trivial, ordelete
U
is derived from T
, orAfter destruct
is called you are not allowed to use t
if T
has const
or reference members (until C++20).
Apart from that there is no restriction on what you do with the class as written as far as I can see.
This answer is based on the draft available at http://eel.is/c++draft/
We can try to apply (by checking each condition) what I've decided to call the "undead object" clause to any previous object that used to exist, here we apply it to the member t
of type T
:
Lifetime [basic.life]/8
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
(8.1) the storage for the new object exactly overlays the storage location which the original object occupied, and
(8.2) the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
(8.3) the original object is neither a complete object that is const-qualified nor a subobject of such an object, and
(8.4) neither the original object nor the new object is a potentially-overlapping subobject ([intro.object]).
Conditions 1 and 2 are automatically guaranteed by the use of placement new on the old member:
struct A {
T t /*initializer*/; (...)
void destruct() { (...)
::new(static_cast<void*>(&t)) T /*initializer*/;
}
The location is the same and the type is the same. Both conditions are easily verified.
Neither A
objects created:
auto a = new A;
...
A b; /*alternative*/
are const qualified complete objects so t
isn't a member of a const qualified complete object. Condition 3 is met.
Now the definition of potentially-overlapping is in Object model [intro.object]/7:
A potentially-overlapping subobject is either:
(7.1) a base class subobject, or
(7.2) a non-static data member declared with the no_unique_address attribute.
The t
member is neither and condition 4 is met.
All 4 conditions are met so the member name t
can be used to name the new object.
[Note that at no point I even mentioned the fact the subobject isn't a const member not its subobjects. That isn't part of the latest draft.
It means that a const sub object can legally have its value changed, and a reference member can have its referent changed for an existing object. This is more than unsettling and probably not supported by many compilers. End note.]