Ответ 1
Формулировка на самом деле является темой отчет о дефекте # 1313, в котором говорится:
Требования к постоянным выражениям в настоящее время не нужны, но должны исключать выражения, которые имеют поведение undefined, такие как арифметика указателя, когда указатели не указывают на элементы одного и того же массива.
Разрешение, являющееся текущей формулировкой, которую мы имеем сейчас, поэтому это явно предназначалось, поэтому какие инструменты нам дают?
Посмотрим, что произойдет, когда мы попытаемся создать переменную constexpr с выражением, содержащим поведение undefined, мы будем использовать clang
для всех следующих примеров. Этот код (посмотреть его в прямом эфире):
constexpr int x = std::numeric_limits<int>::max() + 1 ;
выдает следующую ошибку:
error: constexpr variable 'x' must be initialized by a constant expression
constexpr int x = std::numeric_limits<int>::max() + 1 ;
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: value 2147483648 is outside the range of representable values of type 'int'
constexpr int x = std::numeric_limits<int>::max() + 1 ;
^
Этот код (посмотреть его в прямом эфире):
constexpr int x = 1 << 33 ; // Assuming 32-bit int
вызывает эту ошибку:
error: constexpr variable 'x' must be initialized by a constant expression
constexpr int x = 1 << 33 ; // Assuming 32-bit int
^ ~~~~~~~
note: shift count 33 >= width of type 'int' (32 bits)
constexpr int x = 1 << 33 ; // Assuming 32-bit int
^
и этот код, который имеет поведение undefined в функции constexpr:
constexpr const char *str = "Hello World" ;
constexpr char access( int index )
{
return str[index] ;
}
int main()
{
constexpr char ch = access( 20 ) ;
}
вызывает эту ошибку:
error: constexpr variable 'ch' must be initialized by a constant expression
constexpr char ch = access( 20 ) ;
^ ~~~~~~~~~~~~
note: cannot refer to element 20 of array of 12 elements in a constant expression
return str[index] ;
^
Хорошо, что компилятор может обнаружить поведение undefined в constexpr, или, по крайней мере, то, что clang
считает undefined. Обратите внимание: gcc
ведет себя так же, как в случае поведения undefined с правым и левым сдвигом, gcc
обычно выдает предупреждение в этих случаях, но все равно видит выражение как постоянное.
Мы можем использовать эту функцию через SFINAE, чтобы определить, может ли выражение добавления вызвать переполнение, следующий надуманный пример был вдохновлен dyp умным ответом здесь:
#include <iostream>
#include <limits>
template <typename T1, typename T2>
struct addIsDefined
{
template <T1 t1, T2 t2>
static constexpr bool isDefined()
{
return isDefinedHelper<t1,t2>(0) ;
}
template <T1 t1, T2 t2, decltype( t1 + t2 ) result = t1+t2>
static constexpr bool isDefinedHelper(int)
{
return true ;
}
template <T1 t1, T2 t2>
static constexpr bool isDefinedHelper(...)
{
return false ;
}
};
int main()
{
std::cout << std::boolalpha <<
addIsDefined<int,int>::isDefined<10,10>() << std::endl ;
std::cout << std::boolalpha <<
addIsDefined<int,int>::isDefined<std::numeric_limits<int>::max(),1>() << std::endl ;
std::cout << std::boolalpha <<
addIsDefined<unsigned int,unsigned int>::isDefined<std::numeric_limits<unsigned int>::max(),std::numeric_limits<unsigned int>::max()>() << std::endl ;
}
в результате чего (см. его в прямом эфире):
true
false
true
Не очевидно, что стандарт требует такого поведения, но, по-видимому, этот комментарий Говарда Хиннанта указывает, что он действительно есть:
[...] и также constexpr, что означает, что UB пойман во время компиляции
Обновить
Как-то я пропустил Проблема 695 Ошибки вычисления времени компиляции в функциях constexpr, которая вращается над формулировкой параграфа 5
, который использовался сказать (акцент мой вперед):
Если во время оценки выражения результат не определяется математически или нет в диапазоне представляемых значений для его типа, поведение undefined, , если не появляется такое выражение, где интегральное постоянное выражение требуется (5.19 [expr.const]), и в этом случае программа плохо сформирована.
и далее:
предназначенный как приемлемое стандартное правило для "оцениваемых во время компиляции", понятие, которое не определено непосредственно Стандартом. Непонятно, что эта формулировка адекватно охватывает функции constexpr.
и более поздняя заметка гласит:
[...] Существует напряженность между желанием диагностировать ошибки во время компиляции и не диагностировать ошибки, которые на самом деле не будут возникать во время выполнения. [...] Консенсус CWG заключался в том, что выражение типа 1/0 следует просто считать непостоянным; любая диагностика может возникнуть в результате использования выражения в контексте, требующем постоянного выражения.
который, если я правильно читаю, подтверждает намерение состоять в том, чтобы иметь возможность диагностировать поведение undefined во время компиляции в контексте, требующем постоянного выражения.
Мы не можем однозначно сказать, что это было намерение, но это действительно означает, что это так. Разница в том, как clang
и gcc
относятся к сдвигам undefined, оставляет сомнение.
Я подал отчет об ошибке gcc: поведение вправо и влево undefined не является ошибкой в constexpr. Хотя похоже, что это соответствует, оно нарушает SFINAE, и мы можем видеть из моего ответа на Является ли это совместимым расширением компилятора для обработки стандартных библиотечных функций, не являющихся constexpr, как constexpr? что расхождение в реализации, наблюдаемое для пользователей SFINAE, представляется нежелательным для комитета.