Подтвердить что ты не робот

Как получить доступ к хранилищу объектов через совокупность

В "Lvalues ​​и rvalues", [basic.lval] (3.10), стандарт С++ содержит список типов, таких как "доступ к сохраненному значению объекта" через glvalue такого типа ( пункт 10). В частности, он говорит:

Если программа пытается получить доступ к сохраненному значению объекта через значение gl другого, чем одно из следующих типов, поведение undefined:

  • динамический тип объекта,

  • [некоторые несущественные сведения о CV и подписанные/неподписанные]

  • тип агрегата или объединения, который включает один из вышеупомянутых типов среди его элементов или нестатических членов данных (включая рекурсивно, элемент или нестатический элемент данных субагрегата или содержащегося объединения),

  • [еще несколько вещей]

Что именно означает "совокупное" правило? Как получить доступ к хранимому значению объекта через glvalue некоторого общего типа агрегата?!

Я представляю что-то вроде этого:

int a = 10;                                      // my "stored value"

struct Foo { char x; float y; int z; bool w; };  // an aggregate

reinterpret_cast<Foo&>(a).y = 0;                 // ???

Не делает ли окончательный листинг значение glvalue "совокупного типа, включающего динамический тип a", и, таким образом, делает это действительным?

4b9b3361

Ответ 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) не называются. 71


71 Это иногда называют каламбуром типа.

В контексте вашего примера он говорит, что если законно преобразовать указатель-to- int в указатель-to-Foo, то ваш reinterpret_cast<Foo&)(a) является законным и создает lvalue. (Параграф 1 говорит нам, что это будет lvalue.) И, как я его прочитал, это преобразование указателя само по себе в соответствии с пунктом 7:

Указатель на объект может быть явно преобразован в указатель на другой тип объекта. Когда prvalue v типа "указатель на T1" является преобразованный в тип "указатель на cv T2", результатом является 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.

Но, я думаю, я ответил на ваш первоначальный вопрос: почему существует общее правило? Это дает вам очень простой способ управлять потенциальными псевдонимами без дальнейшего анализа указателей.

Ответ 2

Элемент предложения относится только к нормальному доступу к членам любого агрегата (struct, class или массив) или union: вам нужно иметь доступ к сохраненным значениям объектов, не вызывая undefined. В условии только оговариваются необходимые условия: по крайней мере один из пунктов должен быть правдой. В нем не указаны достаточные условия, т.е. Помимо этих условий могут потребоваться и другие условия.