Вот правило ([basic.lval] / 8) в форме C ++ 17, но оно выглядит аналогично в других стандартах («lvalue» вместо «glvalue» в C ++ 98):
8 Если программа пытается получить доступ к сохраненному значению объекта через glvalue, отличный от одного из следующих типов, поведение не определено:
(8.4) - тип, который является типом со знаком или без знака, соответствующим динамическому типу объекта
Правило звучит как «У вас будет UB, если вы не сделаете X», но это не значит, что если вы сделаете X, вы не получите UB, как можно было ожидать! И действительно, выполнение X является условным или безусловным UB, в зависимости от версии стандарта.
Давайте посмотрим на следующий код:
int i = -1;
unsigned j = reinterpret_cast<unsigned&>(i);
Каково поведение этого кода?
C ++ 98 и C ++ 11
[expr.reinterpret.cast] / 10 (/ 11 в C ++ 11) (акцент мой):
Выражение lvalue типа T1 может быть приведено к типу «ссылка на T2», если выражение типа «указатель»
в T1 »можно явно преобразовать в тип« указатель на T2 »с помощью reinterpret_cast. Это
ссылка приведение reinterpret_cast (x) имеет тот же эффект, что и преобразование
* reinterpret_cast (& x) со встроенными операторами & и *. Результатом является lvalue, который относится
к тому же объекту, что и источник lvalue , но с другим типом .
Итак, reinterpret_cast<unsigned&>(i)
lvalue относится к int
объекту i
, но с типом usigned
. Для инициализации необходимо значение инициализирующего выражения, что формально означает, что преобразование lvalue в rvalue применяется к lvalue.
* 1 033 * [conv.lval] / 1:
Значение l , не являющееся функцией, не являющееся массивом типа T, может быть преобразовано в значение . Если Т неполный
типа, программа, которая требует этого преобразования, плохо сформирована. Если объект, на который ссылается lvalue, не является
объект типа T и не является объектом типа, производного от T, или, если объект неинициализирован, программа
что требует этого преобразования имеет неопределенное поведение .
Наше lvalue типа unsigned
не относится к объекту типа unsigned
, что означает, что поведение не определено.
C ++ 14 и C ++ 17
В этих стандартах ситуация немного сложнее, но правила немного смягчены. [expr.reinterpret.cast] / 11 говорит то же самое:
Результат ссылается на тот же объект, что и источник glvalue, но с указанным типом.
Формулировка об UB была удалена из [conv.lval] / 1:
Значение без функции, не массив типа T может быть преобразовано в значение . Если T - неполный тип, программа, которая требует этого преобразования, плохо сформирована. Если T - это не относящийся к классу тип, типом prvalue является cv-неквалифицированная версия T . В противном случае тип значения - T.
Но какое значение читает преобразование L-в-R? [conv.lval] / (2.6) (/(3.4) в C ++ 17) отвечает на этот вопрос:
… значение, содержащееся в объекте, указанном в glvalue, является результатом prvalue
unsigned
lvalue reinterpret_cast<unsigned&>(i)
указывает на объект i
int
со значением -1
, а значение, полученное в результате преобразования L-в-R, имеет тип unsigned
. [expr] / 4 говорит:
Если во время вычисления выражения результат не определен математически или не находится в диапазоне представимых значений для его типа, поведение не определено.
-1
определенно не входит в диапазон представимых значений для типа unsigned
выражения prvalue, поэтому поведение не определено.
Поведение было бы определено, если бы объект i
содержал значение из диапазона [0, INT_MAX].
тот же гeasoning применяется в случае, когда к объекту unsigned
обращаются через значение int
glvalue. Это UB в C ++ 98 и C ++ 11 и UB в C ++ 14 и C ++ 17, если только значение объекта не находится в диапазоне [0, INT_MAX].
Таким образом, в отличие от распространенного мнения о том, что это правило наложения имен позволяет интерпретировать объект как содержащий значение соответствующего типа со знаком / без знака, оно не допускает этого. Для значений в диапазоне [0, INT_MAX] объекты со знаком и без знака имеют одинаковое представление (" Диапазон неотрицательных значений целого типа со знаком - это поддиапазон соответствующего целого типа без знака, представление одно и то же значение в каждом из этих двух типов одинаково", - говорит [basic.fundamental] / 3 в C ++ 17). Трудно назвать такой доступ «реинтерпретацией», не говоря уже о том, что это был безусловный UB до C ++ 14.
Какова цель правила ([basic.lval] / (8.4)) тогда?