Is it allowed to reuse storage of a non-static data member 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;
A() {
t.~T();
u = ::new(static_cast<void*>(&t)) U /*initializer*/;
}
~A() {
u->~U();
::new(static_cast<void*>(&t)) T /*initializer*/;
}
A(const A&) = delete;
A(A&&) = delete;
A& operator=(const A&) = delete;
A& operator=(A&&) = delete;
};
int main() {
auto a = new A;
*(a->u) = /*some assignment*/;
delete a; /*optional*/
A b; /*alternative*/
*(b.u) = /*some assignment*/; /*alternative*/
}
What conditions do object types T
and U
need to satisfy in addition to the static_assert
s, 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. Of course using it while its storage is occupied by a different type is not allowed.
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.
See also my other question regarding a modified version that does not execute the placement-news during construction/destruction of the enclosing object, since that seems to have caused complications according to some comments.
Concrete example as requested in comments demonstrating the wider question for a subset of types that I think represent different cases of interest:
#include<new>
#include<type_traits>
struct non_trivial {
~non_trivial() {};
};
template<typename T, bool>
struct S {
T t{};
S& operator=(const S&) { return *this; }
};
template<bool B>
using Q = S<int, B>; // alternatively S<const int, B> or S<non_trivial, B>
using T = Q<true>;
using U = Q<false>;
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;
U* u;
A() {
t.~T();
u = ::new(static_cast<void*>(&t)) U;
}
~A() {
u->~U();
::new(static_cast<void*>(&t)) T;
}
A(const A&) = delete;
A(A&&) = delete;
A& operator=(const A&) = delete;
A& operator=(A&&) = delete;
};
int main() {
auto a = new A;
*(a->u) = {};
delete a; /*optional*/
// A b; /*alternative*/
// *(b.u) = {}; /*alternative*/
}