Important clarification: some commenters seem to think that I am copying from a union. Look carefully at the memcpy, it copies from the address of a plain o
At most one member of a union can be active, and it is active during its lifetime.
In the C++14 standard (§ 9.3, or 9.5 in the draft), all non-static union members are allocated as if they were the sole member of a struct, and share the same address. This does not begin the lifetime, but a non-trivial default constructor (which only one union member can have) does. There is a special rule that assigning to a union member activates it, even though you could not normally do this to an object whose lifetime has not begun. If the union is trivial, it and its members have no non-trivial destructors to worry about. Otherwise, you need to worry about when the lifetime of the active member ends. From the standard (§ 3.8.5):
A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. [... I]f there is no explicit call to the destructor or if a delete-expression is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.
It is safer in general to explicitly call the destructor of the currently-active member, and make another member active with placement new. The standard gives the example:
u.m.~M();
new (&u.n) N;
You can check at compile time whether the first line is necessary with std::is_trivially_destructible. By a strict reading of the standard, you can only begin the lifetime of a union member by initializing the union, assigning to it, or placement new, but once you have, you can safely copy a trivially-copyable object over another using memcpy(). (§ 3.9.3, 3.8.8)
For trivially-copyable types, the value representation is a set of bits in the object representation that determines the value, and the object interpretation of T is a sequence of sizeof(T) unsigned char objects. The memcpy() function copies this object representation. All non-static union members have the same address, and you can use that address as a void* to storage after it has been allocated and before the object’s lifetime begins (§ 3.8.6), so you can pass it to memcpy() when the member is inactive. If the union is a standard-layout union, the address of the union itself is the same as the address of its first non-static member, and therefore all of them. (If not, it is interconvertible with static_cast.)
If a type has_unique_object_representations, it is trivially-copyable, and no two distinct values share the same object representation; that is, no bits are padding.
If a type is_pod (Plain Old Data), then it is trivially-copyable and has standard layout, so its address is also the same as the address of its first non-static member.
In C, we have a guarantee that we can read inactive union members of a compatible type to the last one written. In C++, we do not. There are a few special cases where it works, such as pointers containing addresses of objects of the same type, signed and unsigned integral types of the same width, and layout-compatible structures. However, the types you used in your example have some extra guarantees: if they exist at all, uint16_t and uint32_t have exact widths and no padding, each object representation is a unique value, and all array elements are contiguous in memory, so any object representation of a uint32_t is also a valid object representation of some uint16_t[2] even though this object representation is technically undefined. What you get depends on endianness. (If you actually want to split up 32 bits safely, you can use bit shifts and bitmasks.)
To generalize, if the source object is_pod, then it can be copied strictly by its object representation and laid over another layout-compatible object at the new address, and if the destination object is the same size and has_unique_object_representations, it is trivially-copyable as well and will not throw away any of the bits—however, there might be a trap representation. If your union is not trivial, you need to delete the active member (only one member of a non-trivial union can have a non-trivial default constructor, and it will be active by default) and use placement new to make the target member active.
Whenever you copy arrays in C or C++, you always want to check for buffer overflow. In this case, you took my suggestion and used static_assert(). This has no run-time overhead. You can also use memcpy_s(): memcpy_s( &u, sizeof(u), &u32, sizeof(u32) ); will work if the source and destination are POD (trivially-copyable with standard layout) and if the union has standard layout. It will never overflow or underflow a union. It will pad out any remaining bytes of the union with zeroes, which can make a lot of the bugs you’re worried about visible and reproducible.