I read about
aligned_storage() on cppreference , but I couldn't figure it out. Explain, please, more simply.
Let's break it down paragraph by paragraph.
Provides the member typedef type, which is a
std::aligned_storage is a primitive type, that is, data is stored in it as a continuous bunch of bytes (like a structure in C), without C++ troubles. Thanks to this, it can be safely copied even through
…suitable for use as uninitialized storage…
Since the wrapper is a primitive type, it has neither a constructor nor a destructor. Accordingly, the wrapper type's constructor will also not be called. As a result, we can only wrap primitive types (alas).
… for any object whose size is at most
Lenand whose alignment requirement is a divisor of
The type being wrapped must 1) fit entirely within the wrapper, and 2) its valid alignment (a multiple of the address in RAM) must be a divisor of
Let's take the following example. Let
Align = 16 . Its divisors are 1, 2, 4, 8 and 16. This means that we can put in this container:
int8_t(alignment – 1 byte),
int16_t(alignment – 2 bytes),
int32_t(alignment – 4 bytes),
int64_t(alignment – 8 bytes),
- or an SSE vector represented as
uint8_t(expected alignment is 16 bytes).
Why was alignment introduced? The processor exchanges with the RAM not bytes, but by blocks of a fixed size 2 n with a certain integer n . If a variable is aligned, then it can be transferred in the least possible number of transfers. Unaligned data may cross a block boundary and require one more transfer.
In addition, some operations (fast aligned loading commands in SSE, for example) generally throw a processor interrupt if there is no alignment to a certain boundary (for SSE – 16 bytes).
It's worth noting that alignment is equal to size only for primitive types. For composite types (structures and unions), the alignment is equal to that of the largest field. This ensures that the longest field is justified in size. Well, since the sizes of primitive types are equal to a power of two (that is, the alignment of large types is a multiple of the alignment of smaller types), then all other fields will be aligned.
The default value of
Alignis the most stringent (the largest) alignment requirement for any object whose size is at most
Align may or may not be specified. The compiler will then align as it would align the closest (but not larger) type. That is, as if we wrote
std::aligned_storage<sizeof(T), alignof(T)> , where
T is some built-in (albeit not existing) type with the size we need.
If the default value is not used,
Alignmust be the value of
alignof(T)for some type
T, or the behavior is undefined.
The third piece of text listed the types allowed to be placed in a particular wrapper specialization at a particular size and alignment. The same paragraph imposes a constraint on the particular type chosen :
Align must have a typical alignment for that type. That is, we cannot place a
Align=6 , assuming that 6 and 3 are valid alignment values - this provokes undefined behavior (an example about alignment for SSE was already given above).
But this snippet also disables setting
Align to a multiple of the actual alignment. For example, we cannot allocate
uint16_t with 4, 8, 12, etc. alignment.
And yes, we can't flatten an SSE vector if we don't have a ready-made, 16-byte type. After all, then we are forced to use
char , whose native alignment is 1 byte (because the type is
char ). We need 16 – hello, undefined behavior? And if we have a ready-made type, the compiler itself will align it to its own size (which coincides with the required alignment), and we don’t need
The behavior is undefined if
Len == 0.
The wrapper must have a non-zero length (otherwise nothing will fit in it).