Информация об изменениях

Сообщение Re[44]: Зачем плюс? от 07.01.2019 14:45

Изменено 07.01.2019 14:50 σ

Re[44]: Зачем плюс?
σ>>>>Будет unsigned int prvalue с отрицательным значением типа signed int.
NI>>>Я, конечно, понимаю, что некоторые вещи в правилах C++ расходятся со здравым смыслом, но не до такой же степени. Тип значения prvalue никак не может отличаться от типа prvalue.
σ>>Для указателей точно может. Вводя http://wg21.link/p0137 хотели, нет, жаждали, чтобы такое стало возможным.
σ>>Побочным эффектом явилось, например, то, что результатом char или unsigned int prvalue может быть значение типа int.

NI>Я сильно сомневаюсь, что задумка была именно такой, в противном случае те, кто это придумывал, совсем больны на голову. Кроме того, такая интерпретация правил в реале не имеет предсказательной силы.


NI>
#include <cstdint>
NI>#include <iostream>

NI>int main()
NI>{
NI>    std::int8_t x = -1;
NI>    std::cout << short(reinterpret_cast<std::uint8_t &>(x)) << std::endl;
NI>}

NI>Будь по-твоему, данная программа должна выводить -1, т.к. преобразование в short целочисленного значения, которое может быть представлено в short, оставляется без изменений. Попробуй найти хоть одну реализацию, дающую такой вывод.

Я слегка погорячился насчёт unsigned int prvalue с отрицательным значением signed int. Там будет неопределённое поведение.
В твоём примере кода тоже неопределённое поведение, причём об этом в стандарте сказано явно. Во всех стандартах, от C++98/03 до C++17.

C++98 и C++11
[expr.reinterpret.cast]/10 (/11 в C++11):
An lvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer
to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. That is, a
reference cast reinterpret_cast<T&>(x) has the same effect as the conversion
*reinterpret_cast<T*>(&x) with the built-in & and * operators. The result is an lvalue that refers
to the same object as the source lvalue, but with a different type.

[conv.lval]/1:
An lvalue (3.10) of a non-function, non-array type T can be converted to an rvalue. If T is an incomplete
type, a program that necessitates this conversion is ill-formed. If the object to which the lvalue refers is not
an object of type T
and is not an object of a type derived from T, or if the object is uninitialized, a program
that necessitates this conversion has undefined behavior.


lvalue типа uint8_t ссылается на объект типа int8_t → UB при lvalue-to-rvalue conversion

В C++98/03 и 11 UB всегда. В следующих стандартах UB зависит от значения объекта. А именно
C++14 и C++17
[expr.reinterpret.cast]/11 без изменений:
The result refers to the same object as the source glvalue, but with the specified type.
Из [conv.lval]/1 исчезло упоминание об UB.
A glvalue of a non-function, non-array type T can be converted to a prvalue. If T is an incomplete type, a program that necessitates this conversion is ill-formed. If T is a non-class type, the type of the prvalue is the cv-unqualified version of T. Otherwise, the type of the prvalue is T.
[conv.lval]/(2.6) (/(3.4) в C++17):
Otherwise, the value contained in the object indicated by the glvalue is the prvalue result.

Я тыкал в это правило утверждая, что результатом будет то-то и то-то и это определённое поведение. Но надо обратить внимание на последнее предложение из [conv.lval]/1 и на [expr]/4:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.

L-to-R, применённый к reinterpret_cast<std::uint8_t &>(x) имеет тип uint8_t, но значение, которое является результатом этого выражения — -1 (the value contained in the object indicated by the glvalue) — is not in the range of representable values for its type. Следовательно, the behavior is undefined.

Эти рассуждения справедливы и для unsigned int prvalue, которое получилось преобразованием lvalue, ссылавшимся на signed int с отрицательным значением. И для попытки читать из переменной с типом int через char glvalue значения, не влезающие в char.
Re[44]: Зачем плюс?
σ>>>>Будет unsigned int prvalue с отрицательным значением типа signed int.
NI>>>Я, конечно, понимаю, что некоторые вещи в правилах C++ расходятся со здравым смыслом, но не до такой же степени. Тип значения prvalue никак не может отличаться от типа prvalue.
σ>>Для указателей точно может. Вводя http://wg21.link/p0137 хотели, нет, жаждали, чтобы такое стало возможным.
σ>>Побочным эффектом явилось, например, то, что результатом char или unsigned int prvalue может быть значение типа int.

NI>Я сильно сомневаюсь, что задумка была именно такой, в противном случае те, кто это придумывал, совсем больны на голову. Кроме того, такая интерпретация правил в реале не имеет предсказательной силы.


NI>
#include <cstdint>
NI>#include <iostream>

NI>int main()
NI>{
NI>    std::int8_t x = -1;
NI>    std::cout << short(reinterpret_cast<std::uint8_t &>(x)) << std::endl;
NI>}

NI>Будь по-твоему, данная программа должна выводить -1, т.к. преобразование в short целочисленного значения, которое может быть представлено в short, оставляется без изменений. Попробуй найти хоть одну реализацию, дающую такой вывод.

Я слегка погорячился насчёт unsigned int prvalue с отрицательным значением signed int. Там будет неопределённое поведение.
В твоём примере кода тоже неопределённое поведение, причём об этом в стандарте сказано явно. Во всех стандартах, от C++98/03 до C++17.

C++98 и C++11
[expr.reinterpret.cast]/10 (/11 в C++11):
An lvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer
to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. That is, a
reference cast reinterpret_cast<T&>(x) has the same effect as the conversion
*reinterpret_cast<T*>(&x) with the built-in & and * operators. The result is an lvalue that refers
to the same object as the source lvalue, but with a different type.

[conv.lval]/1:
An lvalue (3.10) of a non-function, non-array type T can be converted to an rvalue. If T is an incomplete
type, a program that necessitates this conversion is ill-formed. If the object to which the lvalue refers is not
an object of type T
and is not an object of a type derived from T, or if the object is uninitialized, a program
that necessitates this conversion has undefined behavior.


lvalue типа uint8_t ссылается на объект типа int8_t → UB при lvalue-to-rvalue conversion

В C++98/03 и 11 UB всегда. В следующих стандартах UB зависит от значения объекта. А именно
C++14 и C++17
[expr.reinterpret.cast]/11 без изменений:
The result refers to the same object as the source glvalue, but with the specified type.
Из [conv.lval]/1 исчезло упоминание об UB.
A glvalue of a non-function, non-array type T can be converted to a prvalue. If T is an incomplete type, a program that necessitates this conversion is ill-formed. If T is a non-class type, the type of the prvalue is the cv-unqualified version of T. Otherwise, the type of the prvalue is T.
[conv.lval]/(2.6) (/(3.4) в C++17):
Otherwise, the value contained in the object indicated by the glvalue is the prvalue result.

Я тыкал в последнее правило утверждая, что результатом будет то-то и то-то и это определённое поведение. Но надо обратить внимание на последнее предложение в [conv.lval]/1 и на [expr]/4:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.

L-to-R, применённый к reinterpret_cast<std::uint8_t &>(x) имеет тип uint8_t, но значение, которое является результатом этого выражения — -1 (the value contained in the object indicated by the glvalue) — is not in the range of representable values for its type. Следовательно, the behavior is undefined.

Эти рассуждения справедливы и для unsigned int prvalue, которое получилось преобразованием lvalue, ссылавшимся на signed int с отрицательным значением. И для попытки читать из переменной с типом int через char glvalue значения, не влезающие в char.