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
In a union, a non-static data member is active if its name refers to an object whose lifetime has begun and has not ended ([basic.life]). At most one of the non-static data members of an object of union type can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time.
At most one member of a union can be active at any one time.
An active member is one whose lifetime has begun and not ended.
Thus, if you end the lifetime of a member of your union, it is no longer active.
If you have no active members, causing the lifetime of another member of the union to begin is well-defined under the standard, and causes it to become active.
The union has allocated storage sufficient for all of its members. They all are allocated as if they where alone, and they are pointer-interconvertible. [class.union]/2.
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated40 or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that represents the address of the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see [class.cdtor]. Otherwise, such a pointer refers to allocated storage ([basic.stc.dynamic.deallocation]), and using the pointer as if the pointer were of type void*, is well-defined.
So you can take a pointer to a union member and treat it as a pointer to allocated storage. Such a pointer may be used to construct an object there, if such a construction is legal.
Placement new is a valid way to construct an object there. memcpy
of trivially copyable types (including POD types) is a valid way to construct an object there.
But, constructing an object there is only valid if it does not violate the rule of there being one active member of the union.
If you assign to a member of a union under certain conditions [class.union]/6 it first ends the lifetime of the currently active member, then starts the lifetime of the assigned-to member. So your u.u32_in_a_union = 0xaaaabbbb;
is legal even if there is another member active in the union (and it makes u32_in_a_union
active).
This isn't the case with placement new or memcpy
, there is no explicit "the lifetime of the active member end" in the union specification. We must look elsewhere:
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.
The question is, is starting the lifetime of a different member of the union "reusing the storage", thus ending the other union members lifetime? In practice, obviously (they are pointer-interconvertable, they share the same address, etc). [class.union]/2.
So I would argue yes.
So creating another object through a void*
pointer (placement new, or memcpy
if legal for the type) ends the lifetime of the alternative members of the union
(if any) (not calling their destructor, but that is usually ok), and makes the pointed-to object active and alive, at once.
It is legal to begin the lifetime of a double
or an array of int16_t
or similar via memcpy
over storage.
The legality of copying an array of two uint16_t
over an uint32_t
or vice versa I will leave to others to argue. Apparently it is legal in C++17. But this object being a union has nothing to do with that legality.
This answer is based off of discussion with @Lorehead below their answer. I felt I should provide an answer that aims directly at I think the core of the problem.