Постоянные выражения
Определяет выражение, которое может быть вычислено во время компиляции.
Такие выражения могут использоваться в качестве нетиповых шаблонных аргументов, размеров массивов и в других контекстах, требующих постоянных выражений, например:
int n = 1; std::array<int, n> a1; // error: n is not a constant expression const int cn = 2; std::array<int, cn> a2; // OK: cn is a constant expression
Ядерные постоянные выражения
Ядерное постоянное выражение — это любое выражение, чьё вычисление не должно вычислять ни одно из следующих:
- указатель
this, за исключением constexpr-функции, которая вычисляется как часть выражения - поток управления, проходящий через объявление переменной со статической или локальной для потока областью хранения, и непригодной в постоянных выражениях
- выражение вызова функции (или конструктора), которая не объявлена как constexpr
constexpr int n = std::numeric_limits<int>::max(); // OK: max() is constexpr constexpr int m = std::time(nullptr); // Error: std::time() is not constexpr
- вызов constexpr-функции, которая объявлена, но не определена
- вызов constexpr-функции/шаблона конструктора, где инстанциация не удовлетворяет требованиям constexpr-функции/конструктора.
- вызов constexpr-виртуальной функции, вызванной на объекте, непригодном в постоянных выражениях и чья продолжительность существования началась вне этого выражения.
- выражение, превышающее определяемые реализацией ограничения
- выражение, чьё вычисление приводит к неопределённому поведению языка (включая переполнение знакового целого, деление на ноль, арифметику указателей за пределами массива и т. д.). Неопределённое поведение стандартной библиотеки может быть не определено.
constexpr double d1 = 2.0 / 1.0; // OK constexpr double d2 = 2.0 / 0.0; // Error: not defined constexpr int n = std::numeric_limits<int>::max() + 1; // Error: overflow int x, y, z[30]; constexpr auto e1 = &y - &x; // Error: undefined constexpr auto e2 = &z[20] - &z[3]; // OK constexpr std::bitset<2> a; constexpr bool b = a[2]; // UB, but unspecified if detected
- (до C++17) лямбда-выражение
- неявное преобразование lvalue в rvalue неявное преобразование, если оно применяется к непеременной glvalue типа литерала, которая ...
- обозначает объект, пригодный в постоянных выражениях,
int main() { const std::size_t tabsize = 50; int tab[tabsize]; // OK: tabsize is a constant expression // because tabsize is usable in constant expressions // because it has const-qualified integral type, and // its initializer is a constant initializer std::size_t n = 50; const std::size_t sz = n; int tab2[sz]; // error: sz is not a constant expression // because sz is not usable in constant expressions // because its initializer was not a constant initializer } - ссылается на непеременной объект, чья продолжительность существования началась во время вычисления этого выражения
- обозначает объект, пригодный в постоянных выражениях,
- неявное преобразование lvalue в rvalue или модификация, применённая к неактивному члену объединения или его подобъекту (даже если у них общая начальная последовательность с активным членом)
- неявное преобразование lvalue в rvalue для объекта с неопределённым значением
- вызов неявного копирующего/перемещающего конструктора/присваивания для объединения, активный член которого является изменяемым (если таковой есть), с продолжительностью существования, начавшейся вне вычисления данного выражения
- (до C++20) выражение присваивания, которое изменило бы активный член объединения
- id-выражение, ссылающееся на переменную или член данных типа ссылки, если ссылка пригодна в постоянных выражениях или её продолжительность существования началась в ходе вычисления данного выражения
- преобразование из указателя на void в любой указатель на объект(до C++26) в указатель на объект
T*если указатель указывает на объект, тип которого аналогиченT(с C++26) - (до C++20)
dynamic_cast -
reinterpret_cast - (до C++20) вызов псевдодеструктора
- (до C++14) оператор инкремента или декремента
- (с C++14) модификация объекта, если объект имеет тип литерала, не являющийся переменной, и его продолжительность существования началась в ходе вычисления выражения
constexpr int incr(int& n) { return ++n; } constexpr int g(int k) { constexpr int x = incr(k); // error: incr(k) is not a core constant // expression because lifetime of k // began outside the expression incr(k) return x; } constexpr int h(int k) { int x = incr(k); // OK: x is not required to be initialized // with a core constant expression return x; } constexpr int y = h(1); // OK: initializes y with the value 2 // h(1) is a core constant expression because // the lifetime of k begins inside the expression h(1) - (с C++20) вызов деструктора или псевдодеструктора для объекта, чья продолжительность существования не началась в ходе вычисления этого выражения
- (до C++20) выражение
typeid, применённое к glvalue полиморфного типа - выражение new или вызов
std::allocator<T>::allocate, если выбранная функция выделения является заменяемой глобальной функцией выделения, и выделенная память освобождается во время вычисления этого выражения(с C++20) - выражение delete или вызов
std::allocator<T>::deallocate, если он освобождает область памяти, выделенную во время вычисления этого выражения(с C++20) - (с C++20) Корутины: выражение co_await или co_yield
- (с C++20) трёхстороннее сравнение, когда результат неопределён
- оператор равенства или отношения, результат которого неопределён
- (до C++14) оператор присваивания или составного присваивания
- выражение throw
- asm-объявление
- вызов макроса
va_arg, неизвестно, можно ли вычислить вызов макросаva_start - оператор
goto - выражение
dynamic_castилиtypeid, которое может вызвать исключение - внутри лямбда-выражения, ссылка на
thisили на переменную, определённую вне этого лямбда-выражения, если эта ссылка будет odr-использованиемvoid g() { const int n = 0; constexpr int j = *&n; // OK: outside of a lambda-expression [=] { constexpr int i = n; // OK: 'n' is not odr-used and not captured here. constexpr int j = *&n; // Ill-formed: '&n' would be an odr-use of 'n'. }; }примечание, что если ODR-использование происходит в вызове функции замыкания, оно не относится к
thisили к переменной внешнего окружения, так как оно обращается к члену данных замыкания вместо этого// OK: 'v' & 'm' are odr-used but do not occur in a constant-expression // within the nested lambda auto monad = [](auto v){ return [=]{ return v; }; }; auto bind = [](auto m){ return [=](auto fvm){ return fvm(m()); }; }; // OK to have captures to automatic objects created during constant expression evaluation. static_assert(bind(monad(2))(monad)() == monad(2)());(с C++17)
Примечание: Сам по себе тот факт, что это ядерное постоянное выражение, не имеет непосредственного семантического смысла: выражение должно быть одним из подмножеств постоянных выражений (см. ниже), чтобы быть использованным в определённых контекстах.
Постоянное выражение
Постоянное выражение — это либо
- lvalue(до C++14)glvalue(с C++14) ядерное постоянное выражение, которое ссылается на
- объект со статической областью хранения, который не является временным, или
| (с C++14) |
- не-сразу-вычисляемую(с C++20) функцию
- prvalue ядерное постоянное выражение, чьё значение удовлетворяет следующим ограничениям:
- если значение является объектом типа класса, каждый нестатический член данных типа ссылки ссылается на сущность, которая удовлетворяет ограничениям для lvalue(до C++14)glvalue(с C++14) выше
- если значение типа указателя, оно содержит
- адрес объекта со статической областью хранения
- адрес после конца объекта со статической областью хранения
- адрес не-сразу-вычисляемой(с C++20) функции
- значение нулевого указателя
| (с C++20) |
- если значение является объектом типа класса или массива, каждый подобъект удовлетворяет этим ограничениям для значений
void test()
{
static const int a = std::random_device{}();
constexpr const int& ra = a; // OK: a is a glvalue constant expression
constexpr int ia = a; // Error: a is not a prvalue constant expression
const int b = 42;
constexpr const int& rb = b; // Error: b is not a glvalue constant expression
constexpr int ib = b; // OK: b is a prvalue constant expression
}Целочисленное постоянное выражение
Целочисленное постоянное выражение — это выражение целочисленного или перечислимого типа без области видимости, неявным образом преобразуемое в prvalue, где преобразованное выражение является ядерным постоянным выражением. Если выражение типа класса используется там, где ожидается целочисленное постоянное выражение, выражение контекстуально неявно преобразуется в целочисленный или перечислимый тип без области видимости.
Следующие контексты требуют целочисленного постоянного выражения:
| (до C++14) |
- длина битовых полей
- инициализаторы перечислений, когда базовый тип не фиксирован
- выравнивание.
Преобразованное константное выражение
Преобразованное константное выражение типа T — это выражение, неявным образом преобразуемое к типу T, где преобразованное выражение является константным выражением, а последовательность неявных преобразований содержит только:
- constexpr пользовательские преобразования (так что класс можно использовать там, где ожидается целочисленный тип)
- преобразования lvalue в rvalue
- целочисленные расширения
- целочисленные преобразования без сужения
| (с C++17) |
- И если происходит какое-либо связывание ссылки, то это прямое связывание (не то, которое создаёт временный объект)
Следующие контексты требуют преобразованного константного выражения:
- выражения case
- инициализаторы перечислений, когда базовый тип фиксирован
| (с C++14) |
- целочисленные и перечислительные (до C++17)нетиповые шаблонные аргументы.
| (с C++26) |
Контекстно преобразованное константное выражение типа bool — это выражение, контекстно преобразуемое к bool, где преобразованное выражение является константным выражением, а последовательность преобразований содержит только вышеперечисленные преобразования.
Следующие контексты требуют контекстно преобразованного константного выражения типа bool:
| (до C++23) |
|
(с C++17) (до C++23) |
| (с C++20) |
Исторические категории
Нижеперечисленные категории константных выражений больше не используются в стандарте начиная с C++14:
- Литеральное константное выражение — это prvalue основное константное выражение не-указательного литерального типа (после преобразований, необходимых контекстом). Литеральное константное выражение типа массива или класса требует, чтобы каждый подобъект был инициализирован константным выражением.
- Ссылка-константное выражение — это lvalue основное константное выражение, которое обозначает объект со статической длиной хранения или функцию.
- Адрес-константное выражение — это prvalue основное константное выражение (после преобразований, необходимых контекстом) типа
std::nullptr_tили указательного типа, который указывает на объект со статической длиной хранения, на позицию после конца массива со статической длиной хранения, на функцию или является нулевым указателем.
Константное подвыражениеКонстантное подвыражение — это выражение, чьё вычисление как подвыражения выражения
| (с C++17) |
Используемое в константных выражениях
В списке выше переменная используется в константных выражениях в точке P, если
- переменная является
- constexpr переменной, или
- она является инициализированной константой переменной
- ссылочного типа или
- с квалификатором const для целочисленного или перечислительного типа
- и определение переменной доступно из
P
| (с C++20) |
Объект или ссылка используются в константных выражениях, если это
- переменная, которая используется в константных выражениях, или
| (с C++20) |
- строковый литерал, или
- не-мутабельный подобъект или член-ссылка любого из вышеперечисленного, или
- временный объект не-переменного const-квалифицированного литерального типа, чья продолжительность жизни продлена до продолжительности жизни переменной, которая используется в константных выражениях.
const std::size_t sz = 10; // sz is usable in constant expressions
Явно константные выражения
Следующие выражения (включая преобразования в целевой тип) являются явными константными выражениями:
- границы массива
- размерности в выражениях new кроме первой
- длина битовых полей
- инициализаторы перечислений
- выравнивание
- case выражения
- нетиповые шаблоные аргументы
- выражения в noexcept спецификациях
- выражения в static_assert объявлениях
- инициализаторы constexpr переменных
| (с C++17) |
| (с C++20) |
- инициализаторы переменных ссылочного типа или const-квалифицированных целочисленных или перечислительных типов, но только если инициализаторы являются константными выражениями
- инициализаторы статических и локальных переменных, но только если все подвыражения инициализаторов (включая вызовы конструкторов и неявные преобразования) являются константными выражениями (то есть, если инициализаторы являются константными инициализаторами)
| Определение, происходит ли вычисление в явно константном контексте, можно обнаружить с помощью Для проверки последних двух условий компиляторы могут сначала выполнить пробную константную оценку инициализаторов. Не рекомендуется полагаться на результат в этом случае. int y = 0; const int a = std::is_constant_evaluated() ? y : 1; // Trial constant evaluation fails. The constant evaluation is discarded. // Variable a is dynamically initialized with 1 const int b = std::is_constant_evaluated() ? 2 : y; // Constant evaluation with std::is_constant_evaluation() == true succeeds. // Variable b is statically initialized with 2 | (с C++20) |
Функции и переменные, необходимые для константной оценки
Следующие выражения или преобразования являются потенциально константными:
- явно константные выражения
- потенциально вычисляемые выражения
- непосредственные подвыражения списка инициализации фигурными скобками (может потребоваться константная оценка, чтобы определить, является ли преобразование сужающим)
- выражения адресной ссылки (унарная
&) которые встречаются внутри шаблонного объекта (может потребоваться константная оценка, чтобы определить, является ли такое выражение зависимым от значения) - подвыражения из вышеперечисленного, которые не являются подвыражениями вложенного невычисляемого операнда
Функция необходима для константной оценки, если она является constexpr-функцией и называется выражением, которое потенциально константно вычисляется.
Переменная необходима для константной оценки, если она является либо constexpr-переменной, или имеет тип не-volatile const-qualified целочисленного типа или типа ссылки, и выражение id-выражение, которое её обозначает, потенциально константно вычисляется.
Определение по умолчанию функции и экземплиация шаблона функции специализации или шаблона переменной специализации(с C++14) инициируются, если функция или переменная(с C++14) необходима для константной оценки.
Примечания
Реализации не имеют права объявлять библиотечные функции как constexpr, если стандарт не указывает, что функция является constexpr. Оптимизация возвращаемого значения с именем (NRVO) запрещена в константных выражениях, в то время как оптимизация возвращаемого значения (RVO) является обязательной.
| Макрос проверки особенностей | Значение | Std | Функция |
|---|---|---|---|
__cpp_constexpr_in_decltype | 201711L |
(C++11) (DR) | Генерация определений функций и переменных, когда они необходимы для константной оценки |
__cpp_constexpr_dynamic_alloc | 201907L | (C++20) | Операции для динамического срока жизни в constexpr-функциях |
__cpp_constexpr | 202306L | (C++26) | Преобразование constexpr из void* к типу constexpr - в сторону стирания типов |
Отчёты о дефектах
Следующие отчёты о дефектах, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применён к | Поведение при публикации | Правильное поведение |
|---|---|---|---|
| CWG 1293 | C++11 | не было определено, могут ли строковые литералы использоваться в константных выражениях | они могут использоваться |
| CWG 1311 | C++11 | volatile glvalues могли использоваться в константных выражениях | запрещено |
| CWG 1312 | C++11 |
reinterpret_cast запрещён в константных выражениях, но приведение к void* и из него могло достичь того же эффекта | запрещены преобразования из типа cv void* в указатель на тип объекта |
| CWG 1313 | C++11 | допускалось неопределённое поведение; вычитание указателей было запрещено | неопределённое поведение запрещено; вычитание указателей на один и тот же массив разрешено |
| CWG 1405 | C++11 | для объектов, которые могут использоваться в константных выражениях, их изменяемые подобъекты также могли использоваться | они не могут использоваться |
| CWG 1454 | C++11 | передача констант через constexpr- функции через ссылки была запрещена | разрешено |
| CWG 1455 | C++11 | преобразованные константные выражения могли быть только prvalue | могут быть lvalue |
| CWG 1456 | C++11 | выражение константы адреса не могло указывать адрес сразу после конца массива | разрешено |
| CWG 1535 | C++11 | выражение typeid, операндом которого является тип полиморфного класса, не было ядром константного выражения, даже если проверка во время выполнения не требуется | ограничение операнда ограничивается glvalue полиморфных типов класса |
| CWG 1581 | C++11 | функции, необходимые для константной оценки, не были обязаны быть определены или проинстанцированы | обязательно |
| CWG 1694 | C++11 | связывание значения временного объекта со ссылкой статического срока жизни было константным выражением | это не константное выражение |
| CWG 1952 | C++11 | стандартные библиотечные неопределённые поведения были обязаны диагностироваться | неопределено, будут ли они диагностироваться |
| CWG 2126 | C++11 | константно инициализированные временные объекты с расширенным сроком жизни типа const-qualified литералов не могли использоваться в константных выражениях | могут использоваться |
| CWG 2167 | C++11 | не-член ссылки, локальные для оценки, делали оценку не constexpr | местные не-член ссылки разрешены |
| CWG 2299 | C++14 | неясно, могут ли макросы в <cstdarg>использоваться в константной оценке |
va_arg запрещены,va_start неопределено |
| CWG 2400 | C++11 | вызов constexpr-виртуальной функции для объекта, не пригодного для константных выражений, и срок жизни которого начался вне выражения, содержащего вызов, мог быть константным выражением | это не константное выражение |
| CWG 2418 | C++11 | не было определено, какие объекты или ссылки, не являющиеся переменными, могут использоваться в константных выражениях | определено |
| CWG 2490 | C++20 | (псевдо) вызовы деструкторов не имели ограничений при константной оценке | ограничение добавлено |
См. также
constexpr спецификатор(C++11) | указывает, что значение переменной или функции может быть вычислено во время компиляции |
|
(C++11)(устарело в C++17)(удалено в C++20) | проверяет, является ли тип литеральным типом (шаблон класса) |
| C документация для Константных выражений | |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/constant_expression