Разрешено ли доступ к общему базовому классу членов профсоюза независимо от сохраненного типа?

Рассмотрим объединение, члены которого имеют общий базовый класс:

struct Base {
    int common;
};

struct DerivedA : Base {};
struct DerivedB : Base {};

union Union {
    DerivedA a;
    DerivedB b;
};

Независимо от того, что союз "содержит" во время выполнения (т.е. какое последнее сохраненное значение было), до тех пор, пока оно содержит что-то, что-то является подклассом Base. Есть ли способ законно использовать эту идею для доступа к полю Base, не зная фактического типа объекта, хранящегося в объединении?

Может быть что-то вроде:

Base* p = reinterpret_cast<Base*>(&u);

... возможно, нет. Возможно это:

Base* p2 = static_cast<Base *>(&u.a);

Является ли это законным, если u.b было последним сохраненным значением?

Я знаю, что существуют специальные правила о "общих начальных последовательностях", которые применяются к объединениям, но неясно, есть ли что-то подобное для базовых классов.

Очевидно, что это не будет работать для множественного наследования, поэтому, возможно, это указание, что оно не будет работать вообще.

Ответ 1

Ваш пример точно так же, как вы его набрали, действительно действителен, но он не позволяет много полезных изменений.

Единственное допустимое преобразование lvalue-to-rval в любой части неактивного члена объединения - это доступ к части общей исходной последовательности этого элемента с активным членом ([class.mem]/23).

Но общая начальная последовательность определяется только для двух структур стандартного макета ([class.mem]/20), и существует довольно много правил для того, что квалифицируется как структура стандартного макета ([класс]/7), Подведение итогов:

  • Класс не может быть полиморфным.

  • У класса может быть не более одного базового класса с одним и тем же типом.

  • У класса может быть нестатический член ссылочного типа.

  • Все нестатические члены класса имеют одинаковое управление доступом.

  • Все нестатические члены, включая унаследованные, сначала объявляются в одном классе.

  • Все базовые классы и нестатические члены, включая унаследованные, подчиняются всем приведенным выше правилам, рекурсивно.

  • Существуют правила, которые говорят, что первый нестатический член структуры стандартного макета имеет тот же адрес, что и структура, и что все нестатические члены союза стандартного макета имеют один и тот же адрес союз. Но если любая комбинация этих правил будет означать, что два объекта одного типа должны иметь один и тот же адрес, содержащая struct/union не является стандартным макетом.

(Пример этого последнего правила:

struct A {};           // Standard-layout
struct B { A a; };     // Standard-layout (and &b==&b.a)
union U { A a; B b; }; // Not standard-layout: &u.a==&u.b.a ??
struct C { U u; };     // Not standard-layout: U is not.

)

Ваши DerivedA и DerivedB являются стандартными макетами, поэтому им разрешено иметь общую начальную последовательность. Фактически, эта общая последовательность является единственным членом int каждого из них, поэтому они на самом деле полностью совместимы с макетами (и поэтому могут быть частью общей исходной последовательности некоторой другой пары структур, содержащей эти два).

Однако одна из самых сложных вещей - это правило о всех членах, принадлежащих к одному классу. Если вы добавите какой-либо нестатический член в DerivedA и/или DerivedB, даже если вы добавите к нему один и тот же тип, измененные структуры (s) уже не являются стандартными макетами, поэтому нет обычной начальной последовательности. Это ограничивает большинство реалистичных причин, по которым вы хотели бы использовать наследование в этом шаблоне.

Ответ 2

Доступ к базовому классу через любой из членов, которые содержат эту базу, является законным, при условии, что используемые структуры являются стандартным расположением.

В приведенном примере структуры являются стандартным макетом, поэтому вы можете получить доступ к базе через либо u.a, либо u.b.