> search for it, and I always forget where it is. [...]
Like Chris Vine said these rules are in section 3.10 p10. The
rules do not interfere with access through union members, as I
explain below, but let me go on to the individual items.
> * The list is not really exhaustive, it has at least one one-way
> reinterpretation.
I assume you're talking about the asymmetric rule involving
aggregate or union types. That rule has its origins in the
original C standard. I agree it looks suspicious. Despite that
I believe there are sensible reasons for the asymmetry. In any
case that clause isn't relevant here because the accesses I'm
talking about are through the union's members, never through the
union as a whole.
> * The reinterpretations supported is where some common part of the
> bitpattern really means the same in the two interpretations,
> e.g. signed versus unsigned interpretation for non-negative integers.
That is true for arbitrary accesses, independent of unions. For
example it's okay to access an (unsigned int) using an (int *).
But this sort of thing is not what's happening when accesses
are done through union members. See next.
> * The reinterpretations of interest via type punning in e.g. a union,
> are generally those not in the strict aliasing rule bullet list.
That statement is true, but actually not important. To see why,
consider this picture:
+--------+--------+--------+--------+--------+--------+--------+--------+
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
+--------+--------+--------+--------+--------+--------+--------+--------+
<---- u.small ---->
<------------- u.large ------------->
<----------------------------------- u --------------------------------->
The region shown as 'u' is a union of 8 bytes. The union type
has two members, 'small' of type uint16_t, and 'large' of type
uint32_t. Suppose we store into u.large and read u.small:
u.large = 23;
std::cout << u.small;
The type of the lvalue is uint16_t. The dynamic type of the
object being read is the type of the u.small object, which is
also uint16_t. This access is allowed under the first bullet
item of 3.10 p10.
To be sure what I just said about dynamic type is true, I read
through the definition of dynamic type, and some other terms like
"object", so as not to be misled. (I hope others will do this
also, to give me a double check.) Because of what region is
accessed and how the access is done, the only candidate object is
u.small, whose dynamic type is uint16_t. That matches the lvalue
type of the access, so we are good to go.
I do have a "however" to add to this, please see below.
> What you quoted seems to be about any reinterpretation at all not
> being permitted in a constant expression.
Yes, it definitely is, but that doesn't affect the reasoning. If
reading one member to access a different member-last-written were
undefined behavior under other circumstances there is no reason
to make it undefined behavior under these circumstances. The
dynamic type rules of 3.10 p10 do not interfere with these cases
of union member access, as I have explained above.
And now for the big "having said that....".
I have gone through the various parts of the C++ standard
checking and double checking the reasoning given above. I am
pretty sure the reasoning is sound.
However.... Different passages in the C++ standard look a bit
schizophrenic about unions. In some places it looks like unions
"hold" only one object at a time. In other places it looks like
unions always have all their objects - with the understanding that
only one member may be the "active" member, but the other members
are still identified with "regions of memory", which is what an
object is. It's easy to see how different people might reach
different conclusions about what the semantics are in these
cases.
In C, the wording regarding unions is more straightforward, and
it's fairly clear what is meant to happen. Moreover, if that
weren't enough, the C standard includes a prominent footnote
which says explicitly that union access through another member
will reinterpret the bits of the member last written. For
whatever reason the C++ standard has chosen to muddy the waters
about how unions are supposed to behave. So I think the best
we can say for sure is that the issue is open to debate, and
the C++ standard deserves a big Defect Report as long as that
is true.
I hope you have enjoyed this long explanation and probably
pointless exercise. :)