Inspecting non-active union members, common initial sequence
Asked Answered
D

1

6

This question is based on this

Consider the following:

struct Hdr { int type; };
struct A { Hdr h; };
union Big {
   Hdr h;
   A a;
};

and suppose that for Big big we know that big.a is the active member of the union. Is the access to big.h.type undefined behavior?

I think is indeed UB, based on:

class.union

... [ Note: One special guarantee is made in order to simplify the use of unions: If a standard-layout union contains several standard-layout structs that share a common initial sequence ([class.mem]), and if a non-static data member of an object of this standard-layout union type is active and is one of the standard-layout structs, it is permitted to inspect the common initial sequence of any of the standard-layout struct members; see [class.mem]. — end note ]

We have a standard layout union which has standard layout member structs but as understand it, the common initial sequence for Hdr and A is empty even though the first data member of A is of type of Hdr.

Am I right in that this is UB ? if not, which point of common initial sequence I misunderstood so that the access big.h.type is defined ?

Different answered 5/2, 2019 at 1:27 Comment(6)
Your citation seems to be saying the opposite to me.Stacey
@Stacey is the common initial sequence recursively applied? I mean, is big.a.h inspected to look for common sequence with big.h ?Different
I think you are right. Bummer. I see no recursion in the definition. And any such words would be reasonably dangerous. Unless "first" or "base" has an unconventional meaning (either including oneself, say).Jalbert
@Different Your logic is sound; mine less-so. That's what I hate about this Language Lawyer stuff. I'm going to toss this onto the "UB, but almost certainly going to work" pile.Stacey
@user4581301: I'd characterize most Common Initial Sequence issues as "The authors of the Standard thought the intention and purpose was sufficiently clear that it wouldn't matter whether the Standard was worded in such a way as to actually mandate such support." Unfortunately, it has become fashionable to subject the Standard to a level of language-lawyering for which it was never designed to be suitable, creating an unresolvable conflict between those who claim the behavior of such actions was never defined, and those who argue that they were universally understood as defined...Helper
...in the days before there was a Standard, and the parts of the Standard which characterize a broader general class of action as UB were not intended to suggest that implementations shouldn't continue to process usefully actions within that class which had universally-understood meanings. It would be possible to satisfy two out of three groups: those who think the action should be UB, those who insist it was never meant to be UB, and those who oppose optional features. Unfortunately, fixing the Standard would require consensus among all three groups, so the present mess remains.Helper
L
4

I see no fault in your interpretation.

As per your quote, this is well defined only if A and Hdr share a common initial sequence that includes Hdr::type.

To quote the rule that defines the common initial sequence:

[class.mem] The common initial sequence of two standard-layout struct ([class.prop]) types is the longest sequence of non-static data members and bit-fields in declaration order, starting with the first such entity in each of the structs, such that corresponding entities have layout-compatible types ...

So, the first members of A and Hdr are common if they - that is int and Hdr - are layout-compatible types. That is specified in

[basic.types] Two types cv1 T1 and cv2 T2 are layout-compatible types if T1 and T2 are the same type (they are not the same type), layout-compatible enumerations (they are not enumerations), or layout-compatible standard-layout class types (only one of them is a class type).

As none apply, int and Hdr are not layout-compatible, and therefore the common initial sequence of A and Hdr is empty and therefore there is no member to which the quoted special guarantee applies.


You could use a wrapper to get around the subtlety of the rule:

union Big {
   struct {
       Hdr h;
   } w;
   A a;
} big;

Here, accessing big.w.h.type would be well defined even if big.a is active. P.S. Anonymous struct would be nice here to make the wrapper invisible. Unfortunately those are ill-formed in standard C++.

Lacedaemonian answered 5/2, 2019 at 1:50 Comment(2)
Yes, because now A and Wrapper share a common sequence that only comprise h. I got interested in my first case because in the comment section of the linked question, it was rejected the fact that big.h.type was undefined. ThanksDifferent
@Different I changed the example to have an unnamed struct, as the name was not necessary. Just commenting to clarify to others that w is what you refer to by Wrapper.Lacedaemonian

© 2022 - 2024 — McMap. All rights reserved.