I'm using std::aligned_storage
as the backing storage for a variant template. The problem is, once I enable -O2
on gcc I start getting warnings of 'dereferencing type-punned pointer will break strict aliasing`.
The real template is much more complex (type checked at runtime), but a minimal example to generate the warning is:
struct foo
{
std::aligned_storage<1024> data;
// ... set() uses placement new, stores type information etc ...
template <class T>
T& get()
{
return reinterpret_cast<T&>(data); // warning: breaks strict aliasing rules
}
};
I'm pretty sure boost::variant
is doing essentially the same thing as this, but I can't seem to find how they avoid this issue.
My questions are:
- If using
aligned_storage
in this way violates strict-aliasing, how should I be using it?
- Is there actually a strict-aliasing problem in
get()
given that there are no other pointer based operations in the function?
- What about if
get()
is inlined?
- What about
get() = 4; get() = 3.2
? Could that sequence be reordered due to int
and float
being different types?
std::aligned_storage
is part of <type_traits>
; like most of the rest of the inhabitants of that header file, it is just a holder for some typedefs and is not meant to be used as a datatype. Its job is to take a size and alignment, and make you a POD type with those characteristics.
You cannot use std::aligned_storage<Len, Align>
directly. You must use std::aligned_storage<Len, Align>::type
, the transformed type, which is "a POD type suitable for for use as uninitialized storage for any object whose size is at most Len
and whose alignment is a divisor of Align
." (Align
defaults to the largest useful alignment greater than or equal to Len
.)
As the C++ standard notes, normally the type returned by std::aligned_storage
will be an array (of the specified size) of unsigned char
with an alignment specifier. That avoids the "no strict aliasing" rule because a character type may alias any other type.
So you might do something like:
template<typename T>
using raw_memory = typename std::aligned_storage<sizeof(T),
std::alignment_of<T>::value>::type;
template<typename T>
void* allocate() { return static_cast<void*>(new raw_memory<T>); }
template<typename T, typename ...Arg>
T* maker(Arg&&...arg) {
return new(allocate<T>()) T(std::forward<Arg>(arg)...);
}