Константные выражения — cppreference.com
Определяет выражение, которое можно вычислить во время компиляции.
Такие выражения могут использоваться как аргументы шаблона, не относящиеся к типу, размеры массива и в других контекстах, требующих константных выражений, например
int n = 1; std::array<int, n> a1; // ошибка: n не является константным выражением const int cn = 2; std::array<int, cn> a2; // OK: cn является константным выражение
Основные константные выражения
Основные константные выражения это любое выражение, оценка которого не будет оценивать одно из следующего:
- указатель
this, за исключением в constexpr функции, которая оценивается как часть выражения - поток управления, который проходит через объявление переменной с не constexpr статической или потоковой длительностью хранения, и неприменимый в константных выражениях
- выражение вызова функции, которое вызывает функцию (или конструктор), которая не объявлена как constexpr
constexpr int n = std::numeric_limits<int>::max(); // OK: max() является constexpr constexpr int m = std::time(nullptr); // Ошибка: std::time() не constexpr
- вызов функции для объявленной, но не определённой функции constexpr
- вызов функции для создания экземпляра шаблона функции/конструктора constexpr, где создание не соответствует требованиям constexpr функции/конструктора.
- (начиная с C++20) вызов виртуальной constexpr функции, вызванной для объекта, не используемого в константных выражениях и чьё время жизни началось вне этого выражения.
- выражение, выходящее за лимиты, определённые реализацией
- выражение, вычисление которого приводит к любой форме неопределённого поведения базового языка (включая переполнение целого числа со знаком, деление на ноль, арифметику указателя вне границ массива и т.д.). Неопределено, обнаруживается ли неопределённое поведение стандартной библиотеки.
constexpr double d1 = 2.0/1.0; // OK constexpr double d2 = 2.0/0.0; // Ошибка: не определено constexpr int n = std::numeric_limits<int>::max() + 1; // Ошибка: переполнение int x, y, z[30]; constexpr auto e1 = &y - &x; // Ошибка: не определено constexpr auto e2 = &z[20] - &z[3]; // OK constexpr std::bitset<2> a; constexpr bool b = a[2]; // Неопределённое поведение, но не указывается, если обнаружено
- (до C++17) лямбда-выражение
- неявное преобразование lvalue-в-rvalue, если оно не применяется к не-volatile glvalue литерального типа, которое ...
- обозначает объект, который можно использовать в константных выражениях,
int main() { const std::size_t tabsize = 50; int tab[tabsize]; // OK: tabsize является константным выражением, // потому что tabsize можно использовать в константных выражениях, // потому что оно имеет интегральный тип с квалификацией const, а // его инициализатор является константным инициализатором std::size_t n = 50; const std::size_t sz = n; int tab2[sz]; // ошибка: sz не является константным выражением, // потому что sz не может использоваться в константных выражениях, // потому что его инициализатор не является константным инициализатором }
- ссылается на не volatile объект, время жизни которого началось в пределах оценки этого выражения
- обозначает объект, который можно использовать в константных выражениях,
- неявное преобразование lvalue-в-rvalue или модификация, применяемая к неактивному элементу union или его подобъекту (даже если он имеет общую начальную последовательность с активным элементом)
- неявное преобразование lvalue-to-rvalue для объекта, значение которого неопределенно
- вызов неявного конструктора/присваивания копированием/перемещением для объединения, активный элемент которого является mutable (если есть), с началом времени жизни вне оценки этого выражения
- (до C++20) выражение присваивания, которое изменяет активный элемент объединения
- выражение-идентификатор, ссылающееся на переменную или элемент данных ссылочного типа, за исключением случаев, когда ссылка может использоваться в константных выражениях или её время жизни началось в пределах оценки этого выражения
- преобразование из указателя на
voidв любой тип указателя на объект (до C++26) в тип указателя на объектT*, если указатель не указывает на объект, тип которого подобенT(смотрите также Примечание ниже) (начиная с C++26) - (до C++20)
dynamic_cast -
reinterpret_cast - (до C++20) вызов псевдодеструктора
- (до C++14) операторы инкремента или декремента
-
(начиная с C++14) изменение объекта, за исключением случаев, когда объект имеет не volatile литеральный тип и его время жизни началось в пределах оценки выражения
constexpr int incr(int& n) { return ++n; } constexpr int g(int k) { constexpr int x = incr(k); // ошибка: incr(k) не является основным константным // выражением, потому что время жизни k // началось вне выражения incr(k) return x; } constexpr int h(int k) { int x = incr(k); // OK: не требуется инициализировать с помощью // основного константного выражения return x; } constexpr int y = h(1); // OK: инициализирует y значением 2 // h(1) является основным константным выражением, потому что // время жизни k начинается внутри выражения h(1)
- (начиная с C++20) вызов деструктора или вызов псевдодеструктора для объекта, время жизни которого не началось при оценке этого выражения
- (до C++20) выражение
typeid, применённое к glvalue полиморфного типа - выражение new или вызов std::allocator::allocate, если только выбранная функция распределения не является заменяемой глобальной функцией распределения, и выделенное хранилище освобождается при вычислении этого выражения (начиная с C++20)
- выражение delete или вызов std::allocator::deallocate, если он не освобождает область памяти, выделенную при вычислении этого выражения (начиная с C++20)
- (начиная с C++20) Сопрограммы: выражение await или выражение yield
- (начиная с C++20) трёхстороннее сравнение, если результат не указан
- оператор равенства или отношения, результат которого не указан
- (до C++14) оператор присваивания или составной оператор присваивания
- выражение throw
- объявление asm
- вызов макроса va_arg, может ли быть оценён вызов макроса va_start, не указано
- оператор
goto - выражение
dynamic_castилиtypeid, которое вызовет исключение - внутри лямбда-выражения ссылка на
thisили на переменную, определённую вне этой лямбды, если эта ссылка была бы использована ODRvoid g() { const int n=0; constexpr int j=*&n; // OK: вне лямбда-выражения [=]{ constexpr int i=n; // OK: 'n' не используется в ODR и здесь не фиксируется. constexpr int j=*&n;// Некорректно: '&n' будет использовать ODR для 'n'. }; }
обратите внимание, что если использование ODR происходит при вызове функции для замыкания, оно не ссылается на
thisили включающую переменную, поскольку вместо этого обращается к элементу данных замыкания// OK: 'v' & 'm' используются ODR, но не встречаются в константном выражении // внутри вложенной лямбда auto monad = [](auto v){return [=]{return v;};}; auto bind = [](auto m){return [=](auto fvm){return fvm(m());};}; // OK, имеется возможность захватывать автоматические объекты, // созданные во время вычисления константного выражения. static_assert(bind(monad(2))(monad)() == monad(2)());
(начиная с C++17)
Примечание: Просто быть основным константным выражением не имеет прямого семантического значения: выражение должно быть одним из подмножеств константных выражений (смотрите ниже) для использования в определённых контекстах.
Константное выражение
Константное выражение это либо
- lvalue (до C++14)glvalue (начиная с C++14) основное константное выражение, которое ссылается на
- объект со статической длительностью хранения, который не является временным, или
|
(начиная с C++14) |
- не немедленная (начиная с C++20) функция
- основное константное выражение prvalue, значение которого соответствует следующим ограничениям:
- если значение является объектом классового типа, каждый нестатический элемент данных ссылочного типа ссылается на сущность, которая соответствует ограничениям для lvalues (до C++14)glvalues (начиная с C++14), упомянутые выше
- если значение имеет тип указателя, оно содержит
- адрес объекта со статической длительностью хранения
- адрес за концом объекта со статической длительностью хранения
- адрес не немедленной (начиная с C++20) функции
- значение нулевого указателя
|
(начиная с C++20) |
- если значение является объектом типа класса или массива, каждый подобъект соответствует этим ограничениям для значений
void test() { static const int a = std::random_device{}(); constexpr const int& ra = a; // OK: a это константное выражение glvalue constexpr int ia = a; // Ошибка: a не является константным выражением prvalue const int b = 42; constexpr const int& rb = b; // Ошибка: b не является константным выражением glvalue constexpr int ib = b; // OK: b это константное выражение prvalue }
Целочисленное константное выражение
Целочисленное константное выражение это выражение целочисленного типа или типа перечисления без области видимости, неявно преобразованное в prvalue, где преобразованное выражение является базовым константным выражением. Если выражение типа класса используется там, где ожидается целочисленное константное выражение, выражение контекстуально неявно преобразуется в целочисленный тип или тип перечисления без области видимости.
Следующие контексты требуют целочисленное константное выражение:
- длины битовых полей
- инициализаторы перечисления, когда базовый тип не фиксирован
- выравнивания.
Преобразованное константное выражение
Преобразованное константное выражение типа T это выражение неявно преобразованное к типу T, где преобразованное выражение является константным выражением, а последовательность неявного преобразования содержит только:
- определяемые пользователем constexpr преобразования (так что класс может использоваться там, где ожидается целочисленный тип)
- преобразования lvalue-в-rvalue
- целочисленные продвижения
- не сужающие целочисленные преобразования
|
(начиная с C++17) |
- И если какая-либо привязка ссылки имеет место, это прямая привязка (а не та, которая создаёт временный объект)
В следующих контекстах требуется преобразованное константное выражение:
- выражения case
- инициализаторы перечислителя, когда базовый тип фиксирован
- целочисленные и перечисляемые (до C++17) аргументы шаблона, не относящиеся к типу.
Контекстно преобразованное константное выражение типа bool это выражение, контекстно преобразованное в bool, где преобразованное выражение является константным выражением, а последовательность преобразования содержит только указанные выше преобразования.
Следующие контексты требуют контекстно преобразованное константное выражение типа bool:
Исторические категории
Категории константных выражений, перечисленные ниже, больше не используются в стандарте начиная с C++14:
- Литеральное константное выражение это базовое константное выражение prvalue литерального типа, не являющегося указателем (после преобразований в соответствии с требованиями контекста). Литеральное константное выражение типа массива или класса требует, чтобы каждый подобъект был инициализирован константным выражением.
- Ссылочное константное выражение это базовое константное выражение lvalue, которое обозначает объект со статической длительностью хранения или функцию.
- Адресное константное выражение это основное константное выражение prvalue (после преобразований, требуемых контекстом) типа std::nullptr_t или типа указателя, который указывает на объект со статической длительностью хранения, указывает за конец массива со статической длительностью хранения, на функцию или является нулевым указателем.
Используемые в константных выражениях
В приведённом выше списке переменная может использоваться в константных выражениях в точке P, если
- переменная это
- переменная constexpr, или
- это переменная с константной инициализацией
- ссылочного типа или
- const-квалифицированного целого или перечислимого типа
- и определение переменной достижимой из
P
|
(начиная с C++20) |
Объект или ссылка могут использоваться в константных выражениях, если они
- переменная, которая может использоваться в константных выражениях, или
- объект строкового литерала, или
- не mutable подобъект или ссылочный элемент из всех вышеперечисленных, или
- временный объект не volatile const-квалифицированного литерального типа, время жизни которого продлено до времени жизни переменной, которую можно использовать в константных выражениях.
const std::size_t sz = 10; // sz можно использовать в константных выражениях
Явно константно вычисляемые выражения
Следующие выражения (включая преобразования в целевой тип) вычисляются явно и константно:
- границы массива
- измерения в выражениях new, кроме первого
- длины битовых полей
- инициализаторы перечислений
- выравнивания
- выражения
case - аргументы шаблона не типы
- выражения в спецификациях
noexcept - выражения в объявлениях
static_assert - инициализаторы переменных constexpr
|
(начиная с C++20) |
- Где также допускается неконстантное выражение, в том числе:
- инициализаторы переменных ссылочного типа, целочисленного или перечислимого типа с квалификацией const, но только если инициализаторы являются константными выражениями
- инициализаторы статических и локальных переменных потока, но только если все подвыражения инициализаторов (включая вызовы конструкторов и неявные преобразования) являются константными выражениями (то есть, если инициализаторы являются константными инициализаторами)
|
То, происходит ли оценка в явно константно оцениваемом контексте, можно определить с помощью std::is_constant_evaluated и Чтобы проверить последние два условия, компиляторы могут сначала выполнить пробную константную оценку инициализаторов. Не рекомендуется в этом случае зависеть от результата. int y = 0; const int a = std::is_constant_evaluated() ? y : 1; // Пробная константная оценка не удалась. Константная оценка отбрасывается. // Переменная a динамически инициализируется 1 const int b = std::is_constant_evaluated() ? 2 : y; // Константная оценка с помощью std::is_constant_evaluation() == true выполняется успешно. // Переменная b статически инициализируется значением 2 |
(начиная с C++20) |
Функции и переменные, нуждающиеся в константной оценке
Следующие выражения или преобразования потенциально вычисляются константно:
- явно константно оценённые выражения
- потенциально оценённые выражения
- немедленные подвыражения списка-инициализации в фигурных скобках (может потребоваться константная оценка, чтобы определить, является ли преобразование сужающим)
- выражения взятия адреса (унарный
&), встречающиеся в шаблонной сущности (может потребоваться константная оценка, чтобы определить, является ли такое выражение зависимым от значения) - подвыражения одного из вышеперечисленных выражений, не являющиеся подвыражениями вложенного неоценённого операнда
Функция нуждается в константной оценке, если эта функция constexpr и именована выражением, которое потенциально может быть оценено константно.
Переменная нуждается в константной оценке, если она является либо переменной constexpr, либо имеет не volatile целочисленный тип с квалификацией const или ссылочный тип и выражение идентификатора, которые означают, что это потенциально константная оценка.
Определение функции по умолчанию и создание специализации шаблона функции или специализации шаблона переменной (начиная с C++14) запускается, если функция или переменная (начиная с C++14) нуждается в константной оценке.
Примечание
Реализациям не разрешается объявлять библиотечные функции как constexpr, если в стандарте не указано, что функция является constexpr
Оптимизация именованного возвращаемого значения (NRVO - Named return value optimization) не допускается в константных выражениях, а оптимизация возвращаемого значения (RVO - return value optimization) является обязательной.
| Макрос тест функциональности | Значение | Стандарт | Комментарий |
|---|---|---|---|
__cpp_constexpr_in_decltype |
201711L |
(C++11) | Генерация определений функций и переменных, когда это необходимо для константной оценки |
__cpp_constexpr_dynamic_alloc |
201907L |
(C++20) | Операции для динамической длительности хранения в функциях constexpr
|
__cpp_constexpr |
202306L |
(C++26) | Приведение constexpr из void*: в сторону стирания типа constexpr
|
Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
| Номер | Применён | Поведение в стандарте | Корректное поведение |
|---|---|---|---|
| CWG 1293 | C++11 | не было указано, можно ли использовать строковые литералы в константных выражениях |
они пригодны для использования |
| CWG 1311 | C++11 | volatile glvalue можно использовать в константных выражениях |
запрещено |
| CWG 1312 | C++11 | reinterpret_cast запрещён в константных выражениях, ноприведение к void* и обратно может дать тот же эффект
|
запрещены преобразования из типа cv void*в тип указателя на объект |
| CWG 1313 | C++11 | разрешено неопределённое поведение; все вычитания указателей были запрещены |
неопределённое поведение запрещено; вычитание указателей одного и того же массива ОК |
| CWG 1405 | C++11 | для объектов, которые можно использовать в константных выражениях, их mutable подобъекты также можно было использовать |
они не пригодны для использования |
| 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-квалифицированных литеральных типов нельзя было использовать в константных выражениях |
можно использовать |
| CWG 2167 | C++11 | ссылки, не являющиеся элементами, локальные для оценки, сделали оценку не constexpr |
разрешены ссылки, не являющиеся элементами |
| CWG 2299 | C++14 | было неясно, можно ли использовать макросы из <cstdarg> в константной оценке |
va_arg запрещено, va_start не указано
|
| CWG 2418 | C++11 | не было указано, какой объект или ссылка, не являющиеся переменными, можно использовать в константных выражениях |
указано |
| CWG 2490 | C++20 | (псевдо) вызовам деструктора не хватало ограничений при константной оценке |
ограничение добавлено |