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 comments there.) This question is a slight modification of the original question removing the issue of special rules during construction/destruction of the enclosing object.
Is it allowed to reuse storage of a non-static data member during the lifetime of its enclosing object and if so under what conditions?
Consider the program
#include<new>
#include<type_traits>
using T = /*some type*/;
using U = /*some type*/;
static_assert(std::is_object_v<T>);
static_assert(std::is_object_v<U>);
static_assert(sizeof(U) <= sizeof(T));
static_assert(alignof(U) <= alignof(T));
struct A {
T t /*initializer*/;
U* u;
void construct() {
t.~T();
u = ::new(static_cast<void*>(&t)) U /*initializer*/;
}
void destruct() {
u->~U();
::new(static_cast<void*>(&t)) T /*initializer*/;
}
A() = default;
A(const A&) = delete;
A(A&&) = delete;
A& operator=(const A&) = delete;
A& operator=(A&&) = delete;
};
int main() {
auto a = new A;
a->construct();
*(a->u) = /*some assignment*/;
a->destruct(); /*optional*/
delete a; /*optional*/
A b; /*alternative*/
b.construct(); /*alternative*/
*(b.u) = /*some assignment*/; /*alternative*/
b.destruct(); /*alternative*/
}
Aside from the static_assert
s assume that the initializers, destructors and assignments of T
and U
do not throw.
What conditions do object types T
and U
need to satisfy additionally, so that the program has defined behavior, if any?
Does it depend on the destructor of A
actually being called (e.g. on whether the /*optional*/
or /*alternative*/
lines are present)?.
Does it depend on the storage duration of A
, e.g. whether /*alternative*/
lines in main
are used instead?
Note that the program does not use the t
member after the placement-new, except in the destructor and the destruct
function. Of course using it while its storage is occupied by a different type is not allowed.
Also note that the program constructs an object of the original type in t
before its destructor is called in all execution paths since I disallowed T
and U
to throw exceptions.
Please also note that I do not encourage anyone to write code like that. My intention is to understand details of the language better. In particular I did not find anything forbidding such placement-news as long as the destructor is not called, at least.