Spec-Zone.ru › C++

Постоянные выражения

Определяет выражение, которое может быть вычислено во время компиляции.

Такие выражения могут использоваться в качестве нетиповых шаблонных аргументов, размеров массивов и в других контекстах, требующих постоянных выражений, например:

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

Ядерные постоянные выражения

Ядерное постоянное выражение — это любое выражение, чьё вычисление не должно вычислять ни одно из следующих:

  1. указатель this, за исключением constexpr-функции, которая вычисляется как часть выражения
  2. поток управления, проходящий через объявление переменной со статической или локальной для потока областью хранения, и непригодной в постоянных выражениях
  3. выражение вызова функции (или конструктора), которая не объявлена как 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
  4. вызов constexpr-функции, которая объявлена, но не определена
  5. вызов constexpr-функции/шаблона конструктора, где инстанциация не удовлетворяет требованиям constexpr-функции/конструктора.
  6. вызов constexpr-виртуальной функции, вызванной на объекте, непригодном в постоянных выражениях и чья продолжительность существования началась вне этого выражения.
  7. выражение, превышающее определяемые реализацией ограничения
  8. выражение, чьё вычисление приводит к неопределённому поведению языка (включая переполнение знакового целого, деление на ноль, арифметику указателей за пределами массива и т. д.). Неопределённое поведение стандартной библиотеки может быть не определено.
    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
  9. (до C++17) лямбда-выражение
  10. неявное преобразование lvalue в rvalue неявное преобразование, если оно применяется к непеременной glvalue типа литерала, которая ...
    1. обозначает объект, пригодный в постоянных выражениях,
      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
      }
    2. ссылается на непеременной объект, чья продолжительность существования началась во время вычисления этого выражения
  11. неявное преобразование lvalue в rvalue или модификация, применённая к неактивному члену объединения или его подобъекту (даже если у них общая начальная последовательность с активным членом)
  12. неявное преобразование lvalue в rvalue для объекта с неопределённым значением
  13. вызов неявного копирующего/перемещающего конструктора/присваивания для объединения, активный член которого является изменяемым (если таковой есть), с продолжительностью существования, начавшейся вне вычисления данного выражения
  14. (до C++20) выражение присваивания, которое изменило бы активный член объединения
  15. id-выражение, ссылающееся на переменную или член данных типа ссылки, если ссылка пригодна в постоянных выражениях или её продолжительность существования началась в ходе вычисления данного выражения
  16. преобразование из указателя на void в любой указатель на объект(до C++26) в указатель на объект T* если указатель указывает на объект, тип которого аналогичен T(с C++26)
  17. (до C++20) dynamic_cast
  18. reinterpret_cast
  19. (до C++20) вызов псевдодеструктора
  20. (до C++14) оператор инкремента или декремента
  21. (с 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)
  22. (с C++20) вызов деструктора или псевдодеструктора для объекта, чья продолжительность существования не началась в ходе вычисления этого выражения
  23. (до C++20) выражение typeid, применённое к glvalue полиморфного типа
  24. выражение new или вызов std::allocator<T>::allocate, если выбранная функция выделения является заменяемой глобальной функцией выделения, и выделенная память освобождается во время вычисления этого выражения(с C++20)
  25. выражение delete или вызов std::allocator<T>::deallocate, если он освобождает область памяти, выделенную во время вычисления этого выражения(с C++20)
  26. (с C++20) Корутины: выражение co_await или co_yield
  27. (с C++20) трёхстороннее сравнение, когда результат неопределён
  28. оператор равенства или отношения, результат которого неопределён
  29. (до C++14) оператор присваивания или составного присваивания
  30. выражение throw
  31. asm-объявление
  32. вызов макроса va_arg, неизвестно, можно ли вычислить вызов макроса va_start
  33. оператор goto
  34. выражение dynamic_cast или typeid, которое может вызвать исключение
  35. внутри лямбда-выражения, ссылка на 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) ядерное постоянное выражение, которое ссылается на
    • объект со статической областью хранения, который не является временным, или
  • объект со статической областью хранения, который является временным, но чьё значение удовлетворяет ограничениям для prvalue ниже, или
(с 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, где преобразованное выражение является ядерным постоянным выражением. Если выражение типа класса используется там, где ожидается целочисленное постоянное выражение, выражение контекстуально неявно преобразуется в целочисленный или перечислимый тип без области видимости.

Следующие контексты требуют целочисленного постоянного выражения:

  • границы массива
  • размерности в выражениях new кроме первой
(до C++14)
  • длина битовых полей
  • инициализаторы перечислений, когда базовый тип не фиксирован
  • выравнивание.

Преобразованное константное выражение

Преобразованное константное выражение типа T — это выражение, неявным образом преобразуемое к типу T, где преобразованное выражение является константным выражением, а последовательность неявных преобразований содержит только:

  • constexpr пользовательские преобразования (так что класс можно использовать там, где ожидается целочисленный тип)
  • преобразования lvalue в rvalue
  • целочисленные расширения
  • целочисленные преобразования без сужения
  • преобразования массива в указатель
  • преобразования функции в указатель
  • преобразования указателей на функции (указатель на функцию noexcept в указатель на функцию)
  • преобразования квалификаций
  • преобразования нулевого указателя из std::nullptr_t
  • преобразования нулевого указателя на член из std::nullptr_t
(с C++17)
  • И если происходит какое-либо связывание ссылки, то это прямое связывание (не то, которое создаёт временный объект)

Следующие контексты требуют преобразованного константного выражения:

  • выражения case
  • инициализаторы перечислений, когда базовый тип фиксирован
  • границы массива
  • размерности в выражениях new кроме первой
(с C++14)
  • целочисленные и перечислительные (до C++17)нетиповые шаблонные аргументы.
  • индекс выражения индексирования пакета и спецификатора индексирования пакета.
(с C++26)

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

Следующие контексты требуют контекстно преобразованного константного выражения типа bool:

  • noexcept спецификации
  • объявления static_assert
(до C++23)
  • constexpr if-выражения
(с C++17)
(до C++23)
  • условные explicit спецификаторы
(с C++20)

Исторические категории

Нижеперечисленные категории константных выражений больше не используются в стандарте начиная с C++14:

  • Литеральное константное выражение — это prvalue основное константное выражение не-указательного литерального типа (после преобразований, необходимых контекстом). Литеральное константное выражение типа массива или класса требует, чтобы каждый подобъект был инициализирован константным выражением.
  • Ссылка-константное выражение — это lvalue основное константное выражение, которое обозначает объект со статической длиной хранения или функцию.
  • Адрес-константное выражение — это prvalue основное константное выражение (после преобразований, необходимых контекстом) типа std::nullptr_t или указательного типа, который указывает на объект со статической длиной хранения, на позицию после конца массива со статической длиной хранения, на функцию или является нулевым указателем.

Константное подвыражение

Константное подвыражение — это выражение, чьё вычисление как подвыражения выражения e не предотвращало бы e от того, чтобы быть основным константным выражением, где e не является ни одним из следующих выражений:

  • throw выражение
  • выражение yield
(с C++20)
  • выражение присваивания
  • выражение запятая
(с C++17)

Используемое в константных выражениях

В списке выше переменная используется в константных выражениях в точке P, если

  • переменная является
  • constexpr переменной, или
  • она является инициализированной константой переменной
    • ссылочного типа или
    • с квалификатором const для целочисленного или перечислительного типа
  • и определение переменной доступно из P
  • и, если P не находится в том же файле трансляции, что и определение переменной (то есть, определение импортировано), переменная не инициализирована для указания, или ссылки на, или имеет (возможно, рекурсивный) подобъект, указывающий или ссылающийся на, сущность локальную для файла трансляции, которая может быть использована в константных выражениях
(с C++20)

Объект или ссылка используются в константных выражениях, если это

  • переменная, которая используется в константных выражениях, или
  • объект шаблона, или
(с C++20)
  • строковый литерал, или
  • не-мутабельный подобъект или член-ссылка любого из вышеперечисленного, или
  • временный объект не-переменного const-квалифицированного литерального типа, чья продолжительность жизни продлена до продолжительности жизни переменной, которая используется в константных выражениях.
const std::size_t sz = 10; // sz is usable in constant expressions

Явно константные выражения

Следующие выражения (включая преобразования в целевой тип) являются явными константными выражениями:

  • границы массива
  • размерности в выражениях new кроме первой
  • длина битовых полей
  • инициализаторы перечислений
  • выравнивание
  • case выражения
  • нетиповые шаблоные аргументы
  • выражения в noexcept спецификациях
  • выражения в static_assert объявлениях
  • инициализаторы constexpr переменных
  • условия constexpr if-выражений
(с C++17)
  • выражения в условных explicit спецификаторах
  • немедленные вызовы
  • выражения ограничений в определениях концепций, вложенных требований и requires-клаузах, при определении соответствия ограничений
(с C++20)
  • инициализаторы переменных ссылочного типа или const-квалифицированных целочисленных или перечислительных типов, но только если инициализаторы являются константными выражениями
  • инициализаторы статических и локальных переменных, но только если все подвыражения инициализаторов (включая вызовы конструкторов и неявные преобразования) являются константными выражениями (то есть, если инициализаторы являются константными инициализаторами)

Определение, происходит ли вычисление в явно константном контексте, можно обнаружить с помощью std::is_constant_evaluated и if consteval(с C++23).

Для проверки последних двух условий компиляторы могут сначала выполнить пробную константную оценку инициализаторов. Не рекомендуется полагаться на результат в этом случае.

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) указывает, что значение переменной или функции может быть вычислено во время компиляции
is_literal_type
(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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API