Ответ 1
Цель этого списка состоит не в том, чтобы предоставить вам альтернативные методы для доступа к объекту, а, как указано в сноске к списку, для перечисления всех способов, которыми объект может быть псевдонимом. Рассмотрим следующий пример:
struct foo
{
char x;
float y;
int z;
bool w;
};
void func( foo &F, int &I, double &D )
{
//...
}
Что говорит этот список, так это то, что доступ к F
может также обращаться к тому же базовому объекту, что и доступ к I
. Это может произойти, если вы передали ссылку на F.z
in для I
, например:
func(F, F.z, D);
С другой стороны, вы можете спокойно предположить, что доступ к F
не подходит для того же базового объекта, что и D
, потому что struct foo
не содержит членов типа double
.
Это правда, даже если какой-то шутник делает это:
union onion
{
struct foo F;
double D;
};
onion o;
int i;
func( o.F, i, o.D ); // [class.union] (9.5) wants a word with you. UB.
Я не уверен, что union
занимает центральное место в вашем вопросе. Но часть перед примером union
подчеркивает, почему существует правило агрегации.
Теперь рассмотрим ваш пример: reinterpret_cast<Foo&>(a).y = 0;
[expr.reinterpret.cast] (5.2.10), в пункте 11 сказано следующее:
Выражение lvalue типа
T1
можно отнести к типу "ссылка наT2
", если выражение типа "указатель наT1
" может быть явно преобразуется в тип "указатель наT2
" с помощьюreinterpret_cast
. Что, ссылочный литойreinterpret_cast<T&>(x)
имеет тот же эффект, что и преобразование*reinterpret_cast<T*>(&x)
со встроенными&
и*
операторов (и аналогично дляreinterpret_cast<T&&>(x)
). Результат относится к тому же объекту, что и исходное значение lvalue, но с другим тип. Результатом является lvalue для ссылочного типа lvalue или rvalue ссылается на тип функции и значение x для rvalue ссылка на тип объекта. Нет временных создано, копия не производится, а конструкторы (12.1) или функции преобразования (12.3) не называются. 7171 Это иногда называют каламбуром типа.
В контексте вашего примера он говорит, что если законно преобразовать указатель-to- int
в указатель-to-Foo
, то ваш reinterpret_cast<Foo&)(a)
является законным и создает lvalue. (Параграф 1 говорит нам, что это будет lvalue.) И, как я его прочитал, это преобразование указателя само по себе в соответствии с пунктом 7:
Указатель на объект может быть явно преобразован в указатель на другой тип объекта. Когда prvalue
v
типа "указатель наT1
" является преобразованный в тип "указатель на cvT2
", результатом являетсяstatic_cast<cv T2*>(static_cast<cv void*>(v))
, если обаT1
иT2
являются стандартными типами макета (3.9) и выравниванием требованияT2
не более строгие, чем требованияT1
. Преобразование prvalue типа "указатель наT1
" на тип "указатель наT2
" (гдеT1
иT2
являются типами объектов, и где требования к выравниваниюT2
отсутствуют более строгие, чем параметрыT1
) и обратно к исходному типу, дает исходное значение указателя. Результат любого другое преобразование указателя не указано.
У вас есть стандартные типы макетов с совместимыми ограничениями выравнивания. Итак, у вас есть тип pun, который дает lvalue. Правило, которое вы указали, само по себе не делает его undefined.
Итак, что может сделать это undefined? Ну, например, в пункте 21 [class.mem] (9.2) нам напоминает, что указатель на стандартный объект структуры компоновки указывает на его начальный элемент и наоборот. Итак, после вашего каламбура типа вы остаетесь ссылкой на Foo
, так что Foo
x
находится в том же месте, что и a
.
И... вот где моя языковая адвокация выказывает. Я знаю, что в доступе к Foo
через эту ссылку franken в лучшем случае определенная или неопределенная реализация. Я не могу найти, где он явно был изгнан в царство поведения undefined.
Но, я думаю, я ответил на ваш первоначальный вопрос: почему существует общее правило? Это дает вам очень простой способ управлять потенциальными псевдонимами без дальнейшего анализа указателей.