is this well-defined or UB?
alignas(alingof(T)) char buffer[sizeof(T)];
auto* p = new(buffer) T;
// use p
p->~T(); // destroy at end
does buffer have to be of type char,unsigned char, or std::byte? or can it be any type like an int?
alignas(alignof(T)) int buffer[sizeof(T)/(sizeof(*buffer) + ((sizeof(T)%sizeof(*buffer)!=0)];
It does not have to be any specific type.
However, there is formally a special case if the array element type is unsigned char
or std::byte
. In that case, creating the T
object will not end the lifetime of the array object. The array object will instead provide storage for the new T
object and the new T
object will be nested within the array object.
For any other element type, including char
, creating the T
object within its storage will end the lifetime of the array. That's not directly a concern for your usage.
If however, the element type has a non-trivial destructor, then ending the lifetime of the array elements is an issue when you reach the point where normally the destructor would be called implicitly. If you do not restore equivalent new objects for each array element so that they are within its lifetime when that point is reached, then the program will have undefined behavior per [basic.life]/10.
(Btw. I think that the linked paragraph should say "another object of the original type within its lifetime" instead of just "another object of the original type". Maybe the usage of "occupy" is supposed to already account for lifetime, though. There is probably more generally a defect in that it isn't clearly specified on which object the implicit destructor call happens. If the implicit destructor call happens outside the lifetime of the object, regardless of whether it is trivial, then the behavior is undefined.)
Also, the element type must not be const
-qualified. Otherwise the attempt at storage reuse has undefined behavior per [basic.life]/11.
And technically you should be writing ::new(static_cast<void*>(buffer)) T
or std::construct_at(reinterpret_cast<T*>(buffer))
instead of new(buffer) T
. Otherwise you might accidentally call the wrong operator new
.
The obvious benefit of char
, unsigned char
or std::byte
is that the size calculation is much easier because their sizes are guaranteed to be 1
. And std::byte
has the additional benefit of making it clear that the array is intended to either store raw bytes of information or to provide storage for another object. char
and unsigned char
are generally arithmetic types.
In any case, trying to read/write the elements of the array after the placement-new will be UB. This is not a way to circumvent the type aliasing rules. Only access through the pointer returned by new
will generally be safe.