> Your quote is describing part of the definition and behavior of lvalues and rvalues.
My quote is from the part that specifies what kinds of aliasing is permitted under the standard:
If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:
... <elided>
The intent of this list is to specify those circumstances in which an object may or may not be aliased. |
> There is no mention of an exception to the way union members may be accessed.
Contrary to what has been stated in this thread, the standard does not have any words to this effect: "You can't write to one member of a union and read from another." It does say:
In a union, at most one of the data members can be active at any time, that is, the value of at most one of the data members can be stored in a union at any time. |
How that data member can be accessed is subject to the aliasing rules that were already specified earlier in the standard. These aliasing rules didn't cover one special case, which is specially stated:
If a standard-layout union contains two or more standard-layout structs that share a common initial sequence, and if the standard-layout union object currently contains one of these standard-layout structs, it is permitted to inspect the common initial part of any of them. Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types and either neither member is a bit-field or both are bit-fields with the same width for a sequence of one or more initial members. |
If standard layout types are involved,
1 2 3 4 5
|
union U // standard layout union
{
A a ; // A is a standard layout type
B b ; // B is a standard layout type
};
|
and the union currently contains an object of type A, it is permissible to inspect the member
b provided it is permissible that an object of type A may be aliased through a glvalue of type B. In particular, the object representation of any object of a standard layout type can be treated as an array of char:
For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes making up the object can be copied into an array of char or unsigned char. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value. [ Example:
1 2 3 4 5 6
|
#define N sizeof(T)
char buf[N];
T obj;
// obj initialized to its original value
std::memcpy(buf, &obj, N); // between these two calls to std::memcpy, obj might be modified
std::memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type holds its original value
|
— end example ] |
This program is well-formed:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
int main()
{
// the dynamic type of j is the same as the the dynamic type of i
{ union U { int i ; int j ; } ; U u ; u.i = 7 ; int v = u.j ; }
// the dynamic type of j is a cv-qualifed version of the the dynamic type of i
{ union U { int i ; volatile int j ; } ; U u ; u.i = 7 ; int v = u.j ; }
// the dynamic type of j is an unsigned type corresponding to the the dynamic type of i
{ union U { int i ; unsigned int j ; } ; U u ; u.i = 7 ; unsigned int v = u.j ; }
// the dynamic type of j is a cv-qualifed unsigned type corresponding to the the dynamic type of i
{ union U { int i ; volatile unsigned int j ; } ; U u ; u.i = 7 ; unsigned int v = u.j ; }
// the dynamic type of j[0] is char
{ union U { int i ; char j[sizeof(int)] ; } ; U u ; u.i = 7 ; int v = u.j[0] ; }
// the dynamic type of j is similar to the dynamic type of i
// (as defined by permissible qualification conversions)
{ union U { int* i ; const int* j ; } ; int v = 7 ; U u ; u.i = &v ; int v2 = *u.j ; }
}
|
Note: These permissible aliasing rules are popularly called 'strict' aliasing. From the gcc manual:
-fstrict-aliasing
Allow the compiler to assume the strictest aliasing rules applicable to the language being compiled. For C (and C++), this activates optimizations based on the type of expressions. In particular, an object of one type is assumed never to reside at the same address as an object of a different type, unless the types are almost the same. For example, an unsigned int can alias an int, but not a void* or a double. A character type may alias any other type. |