I\'m trying to build a class template that packs a bunch of types in a suitably large char array, and allows access to the data as individual correctly typed references. Now, a
Using a union is almost never a good idea if you want to stick with strict conformance, they have stringent rules when it comes to reading the active member (and this one only). Although it has to be said that implementations like to use unions as hooks for reliable behaviour, and perhaps that is what you are after. If that is the case I defer to Mike Acton who has written a nice (and long) article on aliasing rules, where he does comment on casting through a union.
To the best of my knowledge this is how you should deal with arrays of char types as storage:
// char or unsigned char are both acceptable
alignas(alignof(T)) unsigned char storage[sizeof(T)];
::new (&storage) T;
T* p = static_cast<T*>(static_cast<void*>(&storage));
The reason this is defined to work is that T
is the dynamic type of the object here. The storage was reused when the new expression created the T
object, which operation implicitly ended the lifetime of storage
(which happens trivially as unsigned char
is a, well, trivial type).
You can still use e.g. storage[0]
to read the bytes of the object as this is reading the object value through a glvalue of unsigned char
type, one of the listed explicit exceptions. If on the other hand storage
were of a different yet still trivial element type, you could still make the above snippet work but would not be able to do storage[0]
.
The final piece to make the snippet sensible is the pointer conversion. Note that reinterpret_cast
is not suitable in the general case. It can be valid given that T
is standard-layout (there are additional restrictions on alignment, too), but if that is the case then using reinterpret_cast
would be equivalent to static_cast
ing via void
like I did. It makes more sense to use that form directly in the first place, especially considering the use of storage happens a lot in generic contexts. In any case converting to and from void
is one of the standard conversions (with a well-defined meaning), and you want static_cast
for those.
If you are worried at all about the pointer conversions (which is the weakest link in my opinion, and not the argument about storage reuse), then an alternative is to do
T* p = ::new (&storage) T;
which costs an additional pointer in storage if you want to keep track of it.
I heartily recommend the use of std::aligned_storage
.