Является ли строгое нарушение псевдонимов псевдонимом структуры как своего первого члена?

Образец кода:

struct S { int x; };

int func()
{
     S s{2};
     return (int &)s;    // Equivalent to *reinterpret_cast<int *>(&s)
}

Я считаю, что это распространено и считается приемлемым. Стандарт гарантирует, что в структуре нет начального заполнения. Однако этот случай не указан в правиле строгого псевдонимов (С++ 17 [basic.lval]/11):

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

  • (11.1) динамический тип объекта,
  • (11.2) cv-квалификационная версия динамического типа объекта,
  • (11.3) тип, аналогичный (как определено в 7.5) для динамического типа объекта,
  • (11.4) тип, который является подписанным или неподписанным типом, соответствующим динамическому типу объекта,
  • (11.5) тип, который является подписанным или неподписанным типом, соответствующим квитанционной версии динамического типа объекта,
  • (11.6) совокупный или тип объединения, который включает один из вышеупомянутых типов среди его элементов или нестатических членов данных (включая рекурсивно элемент или нестатический элемент данных субагрегата или содержащегося объединения),
  • (11.7) тип, который является (возможно, cv-квалифицированным) типом базового класса динамического типа объекта,
  • (11.8) char, unsigned char или std :: byte type.

Кажется очевидным, что объект s имеет доступ к сохраненному значению.

Типы, перечисленные в пунктах пули, являются типом glvalue, выполняющим доступ, а не типом объекта, к которому осуществляется доступ. В этом коде тип glvalue является int который не является агрегатным или объединенным типом, исключая 11.6.

Мой вопрос: правильно ли этот код, и если да, то в каком из перечисленных пунктов можно разрешить?

Ответ 1

Поведение броска сводится к [expr.static.cast]/13;

PRvalue типа "указатель на cv1 void " может быть преобразован в prvalue типа "указатель на cv2 T ", где T - тип объекта, а cv2 - это то же самое cv-qualification, что и более высокая cv-квалификация, чем cv1. Если исходное значение указателя представляет адрес A байта в памяти, и A не удовлетворяет требованию выравнивания T, то результирующее значение указателя не указывается. В противном случае, если исходное значение указателя указывает на объект a, и есть объект b типа T (игнорирующий cv-qualification), который является взаимно конвертируемым с a, результатом является указатель на b. В противном случае значение указателя не изменяется при преобразовании.

Определение взаимно-обратимого указателя:

Два объекта a и b являются взаимопереключаемыми с указателем, если:

  • они являются одним и тем же объектом или
  • один представляет собой объект объединения, а другой - нестатический элемент данных этого объекта или
  • один является объектом класса стандартного макета, а второй является первым нестатическим членом данных этого объекта или, если у объекта нет нестатических элементов данных, первый подобъект базового класса этого объекта или
  • существует объект c такой, что a и c являются взаимно обратимыми для указателей, а c и b являются взаимно обратимыми.

Таким образом, в исходном коде s и sx являются взаимно sx для указателей, и из этого следует, что (int &)s самом деле обозначает sx.

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

Ответ 2

Я думаю, что это в expr.reinterpret.cast # 11

Выражение glval типа T1, обозначающее объект x, может быть передано типу "ссылка на T2", если выражение типа "указатель на T1" может быть явно преобразовано в тип "указатель на T2" с использованием reinterpret_cast. В результате получается *reinterpret_cast<T2 *>(p) где p является указателем на x типа "указатель на T1". Временное создание не производится, копирование не производится, а конструкторы или функции преобразования не называются [1].

[1] Это иногда называют калом типа, когда результат ссылается на тот же объект, что и исходный glvalue

Поддержка ответа @MM об указателе-incovertible:

из cppreference:

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

struct S { int a; } s;


int* p = reinterpret_cast<int*>(&s); // value of p is "pointer to s.a" because s.a
                                     // and s are pointer-interconvertible
*p = 2; // s.a is also 2

против

struct S { int a; };

S s{2};
int i = (int &)s;    // Equivalent to *reinterpret_cast<int *>(&s)
                     // i doesn't change S.a;