Is it defined behavior to access a base class in a union if a layout-compatible derived class member is active?

Viewed 111

Take e.g. the following code:

class B {
  void *ptr = nullptr;
 public:
  auto get() { return ptr; }
};
class D : public B {};

union U {
  B b;
  D d;
};

void *func() {
  U u{.d = D{}};
  return u.b.get(); // ???
}

I am wondering if the line marked with ??? has undefined behavior.

The C++20 standard says in [class.union.general]:

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.

Since d is the active data member at this point, this would imply that it's not allowed to read b. But b and d are layout-convertible, so we know that we could still access it's data member by [class.mem.general]:

In a standard-layout union with an active member of struct type T1, it is permitted to read a non-static data member m of another union member of struct type T2 provided m is part of the common initial sequence of T1 and T2; the behavior is as if the corresponding member of T1 were nominated.

I don't think that this is relevant though since I want to access a member function, not a data member. (At least not directly.)

Now the B base class subobject of d is the first subobject of d and therefore pointer-interconvertible with d, which is pointer-interconvertible with u and therefore b. So we end up with two pointer-interconvertible objects of the same type. Is there some rule which allows this access based on that? The only rule I could find about that is [basic.compound]:

If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_cast.

Which only seems to allow reinterpret_cast<B*>(&u.b)->get() which feels silly.

So is there some rule which allows this or does it rely on undefined behavior and/or compiler extensions?

(I am mostly interested in C++20 or newer drafts)

0 Answers
Related