curiousguy
curiousguy

Reputation: 8268

Is any use of a union clearly well defined, in any revision of C++?

Consider a simple union with a changed "active member":

union U {
  int i;
  char *p;
};

U u = { 1 };
u.p = 0;

Is there any revision of the C++ standard that can properly define what happens here?

In particular, what is u.p semantically? It's a lvalue at compile time, but what does its evaluation refer to at run time?

Can a pointer object exist in u before it's assigned to?

Can objects exist before their lifetime even begins?

Can two scalar objects (of distinct types) coexist at the same time at the same address?

Upvotes: 0

Views: 215

Answers (2)

markt1964
markt1964

Reputation: 2826

C++11 essentially emasculated the union type with the introduction of object lifetimes and really did not give us any means to do the same thing we used to take for granted in C until C++20, when they introduced bit_cast.

C99 explicitly defined as part of the core language what everyone was already assuming it did. Yet, C++11 made the operation explicitly undefined, and gave no clearly defined way to accomplish the same thing (type punning) until the introduction of std::bit_cast.

While you can implement a working bit_cast yourself in C++11 through C++17, using memcpy, doing so would mean that one would usually have to sacrifice being constexpr. Also, the implementation that is most often cited as a way too implement it required the "To" parameter to be trivially constructible, while the definition of std::bit_cast required, as was perfectly reasonable for what unions were always used for (even before C99), that both "To" and "From" be trivially copiable.

Upvotes: -1

Igor Tandetnik
Igor Tandetnik

Reputation: 52471

u.p refers to storage allocated for an object whose lifetime has not yet started, as permitted by [basic.life]/7: "Before the lifetime of an object has started but after the storage which the object will occupy has been allocated... any glvalue that refers to the original object may be used but only in limited ways."

Then there's the special magic by which an assignment to a union member starts the lifetime of the object:

[class.union]/5 When the left operand of an assignment operator involves a member access expression ([expr.ref]) that nominates a union member, it may begin the lifetime of that union member, as described below...

In an assignment expression of the form E1 = E2 that uses either the built-in assignment operator ([expr.ass]) or a trivial assignment operator ([class.copy.assign]), for each element X of S(E1), if modification of X would have undefined behavior under [basic.life], an object of the type of X is implicitly created in the nominated storage; no initialization is performed and the beginning of its lifetime is sequenced after the value computation of the left and right operands and before the assignment.

Upvotes: 3

Related Questions