формулировка фактически является предметом defect report #1313, который говорит:
The requirements for constant expressions do not currently, but should, exclude expressions that have undefined behavior, such as pointer arithmetic when the pointers do not point to elements of the same array.
Резолюция является нынешняя формулировка у нас есть сейчас, так что это явно было задумано, так что же инструменты, что это дает нам?
Давайте посмотрим, что происходит, когда мы пытаемся создать constexpr переменную с выражением, которое содержит неопределенное поведение, мы будем использовать clang
для всех следующих примеров. Этот код (see it live):
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 ;
^
Этот код (see it live):
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
^
и этот код, который имеет неопределенное поведение в функции 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] ;
^
Ну что полезно компилятор может обнаружить неопределенное поведение в constexpr , или, по крайней мере, то, что clang
считает, не определено.Примечание: gcc
ведет себя одинаково, за исключением случаев неопределенного поведения с правым и левым сдвигом, gcc
обычно выдает предупреждение в этих случаях, но все равно видит выражение как постоянное.
Мы можем использовать эту функцию для определения, будет ли выражение добавления вызвать переполнение через SFINAE, следующий надуманный пример был вдохновлен DYP-х clever answer here:
#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 ;
}
что приводит к (see it live):
true
false
true
Не очевидно, что стандарт требует такого поведения, но, по-видимому, это comment by Howard Hinnant указывает на это действительно есть:
[...] and is also constexpr, meaning UB is caught at compile time
Update
Как-то я пропустил Issue 695 Compile-time calculation errors in constexpr functions, который вращается над формулировкой раздела 5
пункта , который используется, чтобы сказать (курсив мой идти вперед):
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, unless such an expression appears where an integral constant expression is required (5.19 [expr.const]), in which case the program is ill-formed.
и далее:
intended as an acceptable Standardese circumlocution for “evaluated at compile time,” a concept that is not directly defined by the Standard. It is not clear that this formulation adequately covers constexpr functions.
и позднее примечание говорит:
[...]There is a tension between wanting to diagnose errors at compile time versus not diagnosing errors that will not actually occur at runtime.[...]The consensus of the CWG was that an expression like 1/0 should simply be considered non-constant; any diagnostic would result from the use of the expression in a context requiring a constant expression.
который, если я читаю правильно подтверждает намерение состояло в том, чтобы быть в состоянии диагностировать неопределенное поведение во время компиляции в контексте, требующий постоянное выражение.
Мы не можем однозначно сказать, что это было целью, но это действительно означает, что это так. Разница в том, как clang
и gcc
относятся к неопределенным сдвигам, оставляет некоторые сомнения.
Я подал заявку gcc bug report: Right and left shift undefined behavior not an error in a constexpr. Хотя похоже, что это соответствует, оно нарушает SFINAE, и из моего ответа на вопрос Is it a conforming compiler extension to treat non-constexpr standard library functions as constexpr? видно, что расхождение в реализации, наблюдаемое для пользователей SFINAE, представляется нежелательным для комитета.
Я думаю, что любая реализация может принимать неверно сформированные программы (которые затем имеют неопределенное поведение). То есть реализация может принимать постоянные выражения с UB в качестве расширения (например, с семантикой, определяемой реализацией, вместо UB). Указав программу как не сформированную, из нее требуется диагностическое сообщение. – dyp
@ dyp: Но причина, по которой диагностика не требуется для UB в целом, заключается в том, что диагностировать ее очень сложно. –
@ dyp стандарт определенно не ясен, хотя комментарий Говарда, с которым я связан, кажется, указывает на то, что это ожидаемое поведение. –