Почему в версии С++ 14 было изменено 1 << 31?

Во всех версиях C и С++ до 2014 года запись

1 << (CHAR_BIT * sizeof(int) - 1)

вызвало поведение undefined, поскольку сдвиг слева определяется как эквивалентный последовательному умножению на 2, и этот сдвиг вызывает сплошное переполнение цепочки:

Результатом E1 << E2 является E1 левое смещение E2 битовых позиций; освобожденные биты заполняются нулями. [...] Если E1 имеет подписанный тип и неотрицательное значение, а E1 × 2 E2 представляется в типе результата, то это результирующее значение; в противном случае поведение undefined.

Однако в С++ 14 текст изменился для <<, но не для умножения:

Значение E1 << E2 - это E1 сдвинутые слева позиции E2; освобожденные биты заполняются нулями. [...] В противном случае, если E1 имеет подписанный тип и неотрицательное значение, а E1 × 2 E2 представляется в соответствующем неподписанном типе тип результата, то это значение, , преобразованное в тип результата, является результирующим значением; в противном случае поведение undefined.

Поведение теперь такое же, как для присвоения вне диапазона для подписанного типа, т.е. как описано в [conv.integral]/3:

Если тип назначения подписан, значение не изменяется, если оно может быть представлено в типе назначения (и ширине битового поля); в противном случае значение определяется реализацией.

Это означает, что он все еще не переносится для записи 1 << 31 (в системе с 32-битным int). Итак, почему это изменение было внесено в С++ 14?

Ответы

Ответ 1

Соответствующая проблема CWG 1457, где обоснование заключается в том, что изменение позволяет использовать 1 << 31 в постоянных выражениях:

Текущая редакция параграфа 5.8 [expr.shift] 2 делает ее undefinedповедение, чтобы создать наиболее отрицательное целое число данного типа посредством сдвиг слева (1) в знаковый бит, хотя это не необычно сделано и работает правильно на большинстве (двухкомпонентные) архитектуры:

... если E1 имеет подписанный тип и неотрицательное значение, а E1 * 2 E2 -   представляемый в типе результата, то это результирующее значение;   в противном случае поведение undefined.

В результате этот метод не может использоваться в постоянном выражении, которое нарушит значительный объем кода.

Константные выражения не могут содержать поведение undefined, что означает, что использование выражения, содержащего UB в контексте, требующем постоянного выражения, делает программу плохо сформированной. libstdС++ numeric_limits::min, например, после этого не удалось скомпилировать в clang.