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

Определение

Выражение, принадлежащее к любой из перечисленных ниже категорий константных выражений, является константным выражением.

Категории константных выражений в C++98

Целочисленное константное выражение (C++98)

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

  • границы массивов (включая размеры в new выражениях, кроме первого)
  • константы меток case
  • длины битовых полей
  • инициализаторы элементов перечисления
  • инициализаторы статических членов данных
  • нетиповые аргументы шаблонов целочисленного типа или типа перечисления

Выражение, удовлетворяющее всем следующим условиям, является целочисленным константным выражением :

  • Оно включает только следующие сущности:
  • литералы арифметических типов
  • элементы перечисления
  • переменные или статические члены данных, удовлетворяющие всем следующим условиям:
    • Они квалифицированы как const.
    • Они не квалифицированы как volatile.
    • Они имеют целочисленный тип или тип перечисления.
    • Они инициализированы константными выражениями.
  • нетиповые параметры шаблона целочисленных типов или типов перечисления
  • выражения sizeof
  • В нем не используются литералы с плавающей точкой, если только они не преобразованы явно к целочисленному типу или типу перечисления.
  • В нем не выполняются преобразования к типам, не являющимся целочисленными типами или типами перечисления.
  • В нем не используются следующие сущности, кроме как в операндах sizeof:
    • функция
    • объект класса
    • указатель
    • ссылка
    • оператор присваивания
    • оператор инкремента
    • оператор декремента
    • оператор вызова функции
    • оператор запятая

Другие категории константных выражений

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

  • выражение, результатом вычисления которого является значение нулевого указателя
  • выражение, результатом вычисления которого является значение нулевого указателя на член
  • арифметическое константное выражение
  • адресное константное выражение
  • константное выражение-ссылка
  • адресное константное выражение для полного объектного типа со сложением или вычитанием целочисленного константного выражения
  • константное выражение-указатель на член

Арифметическое константное выражение — это выражение, удовлетворяющее требованиям к целочисленному константному выражению, за следующими исключениями:

  • Можно использовать литералы с плавающей точкой без явного преобразования.
  • Можно выполнять преобразования к типам с плавающей точкой.

Адресное константное выражение — это выражение типа указателя, удовлетворяющее всем следующим условиям:

  • Указатель указывает на lvalue, обозначающее объект со статической длительностью хранения, строковый литерал или функцию. Объект не является подобъектом типа, не являющегося типом POD-класса.
  • Указатель создан одним из следующих способов:
    • явно с помощью оператора взятия адреса
    • неявно с помощью нетипового параметра шаблона типа указателя
    • с помощью выражения типа массива или функции
  • Выражение не вызывает никаких функций.
  • В выражении используются явные преобразования указателей (за исключением dynamic_cast) и следующие операторы без доступа к объекту-результату:
    • оператор индексации
    • оператор разыменования
    • оператор взятия адреса
    • оператор доступа к члену
  • Если используется оператор индексации, один из его операндов является целочисленным константным выражением.

Константное выражение-ссылка — это выражение ссылочного типа, удовлетворяющее всем следующим условиям:

  • Ссылка обозначает объект со статической длительностью хранения, нетиповой параметр шаблона ссылочного типа или функцию. Ссылка не обозначает член или базовый класс типа, не являющегося типом POD-класса.
  • Выражение не вызывает никаких функций.
  • В выражении используются явные преобразования ссылок (за исключением dynamic_cast) и следующие операторы без доступа к объекту-результату:
    • оператор индексации
    • оператор разыменования
    • оператор взятия адреса
    • оператор доступа к члену
  • Если используется оператор индексации, один из его операндов является целочисленным константным выражением.

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

(до C++11)

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

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

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

  • Каждая составляющая ссылка ссылается на сущность, являющуюся допустимым результатом константного выражения.
  • Ни одно составляющее значение скалярного типа не является неопределённым значением.
  • Каждое составляющее значение типа указателя является одним из следующих значений:
    • адрес объекта со статической длительностью хранения
    • адрес за концом объекта со статической длительностью хранения
    • адрес немедленной функции(начиная с C++20)
    • значение нулевого указателя
  • Ни одно составляющее значение типа указателя на член не обозначает немедленную функцию.
(начиная с C++11)
(до C++26)

Константное выражение — это либо glvalue основного константного выражения, ссылающееся на объект или функцию, не являющуюся немедленной, либо prvalue основного константного выражения, значение которого удовлетворяет следующим ограничениям:

  • Каждая составляющая ссылка ссылается на объект или функцию, не являющуюся немедленной.
  • Ни одно составляющее значение скалярного типа не является неопределённым или ошибочным значением.
  • Ни одно составляющее значение типа указателя не является указателем на немедленную функцию или недопустимым значением указателя.
  • Ни одно составляющее значение типа указателя на член не обозначает немедленную функцию.
(начиная с C++26)

При определении того, является ли выражение константным, предполагается, что устранение копирования не выполняется.

Определение константных выражений в C++98 целиком находится в сворачиваемом блоке. Следующее описание относится к C++11 и более поздним версиям C++.

Литеральный тип

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

  • возможно, cv-квалифицированный void
(начиная с C++14)
  • скалярный тип
  • ссылочный тип
  • массив литерального типа
  • возможно, cv-квалифицированный тип класса, удовлетворяющий всем следующим условиям:
    • У него есть тривиальный деструктор(до C++20)constexpr-деструктор(начиная с C++20).
    • Все его нестатические невариативные члены данных и базовые классы имеют нелетучие литеральные типы.
    • Он является одним из следующих типов:
  • тип замыкания
(начиная с C++17)
  • тип объединения-агрегата, удовлетворяющий одному из следующих условий:
    • У него нет вариативных членов.
    • У него есть хотя бы один вариативный член нелетучего литерального типа.
  • тип агрегата, не являющийся объединением, каждый член анонимного объединения которого удовлетворяет одному из следующих условий:
    • У него нет вариативных членов.
    • У него есть хотя бы один вариативный член нелетучего литерального типа.
  • тип, имеющий хотя бы один constexpr-конструктор (шаблон), не являющийся конструктором копирования или перемещения

Внутри константного выражения можно создавать только объекты литеральных типов.

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

Основное константное выражение — это любое выражение, вычисление которого не приводит к вычислению какой-либо из следующих языковых конструкций:

  1. указатель this, за исключением constexpr-функции, вычисляемой как часть выражения, или случая, когда он встречается в неявном или явном выражении доступа к члену класса
  2. (начиная с C++23) поток управления, проходящий через объявление блочной переменной со статической длительностью хранения или длительностью хранения потока, которая не является доступной в константных выражениях
  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-функции для объекта, динамический тип которого неизвестен для constexpr
  7. выражение, которое превысило бы ограничения, определяемые реализацией
  8. выражение, вычисление которого приводит к любой форме неопределённого или ошибочного(начиная с C++26) поведения основного языка, за исключением потенциального неопределённого поведения, вызванного стандартными атрибутами.
    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-to-rvalue, кроме случаев, когда оно применяется к...
    1. glvalue типа (возможно, cv-квалифицированного) std::nullptr_t
    2. 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
      }
      
    3. glvalue нелетучего литерального типа, ссылающемуся на нелетучий объект, время жизни которого началось в ходе вычисления этого выражения
  11. преобразование lvalue-to-rvalue или модификация, применённые к неактивному члену объединения или его подобъекту (даже если он входит в общую начальную последовательность с активным членом)
  12. преобразование lvalue-to-rvalue для объекта, значение которого неопределено
  13. вызов неявного конструктора/оператора присваивания копирования или перемещения для объединения, активный член которого является изменяемым (если он есть), если время жизни началось вне вычисления этого выражения
  14. (до C++20) выражение присваивания, изменяющее активный член объединения
  15. преобразование указателя на void в тип указателя на объект T*, если только указатель не содержит значение нулевого указателя или не указывает на объект, тип которого подобен T(начиная с C++26)
  16. dynamic_cast , операндом которого является glvalue, ссылающееся на объект, динамический тип которого неизвестен для constexpr(начиная с C++20)
  17. reinterpret_cast
  18. (до C++20) вызов псевдодеструктора
  19. (до C++14) оператор инкремента или декремента
  20. (начиная с 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)
    
  21. (начиная с C++20) вызов деструктора или псевдодеструктора для объекта, время жизни которого не началось в ходе вычисления этого выражения
  22. выражение typeid, применённое к glvalue полиморфного типа, если этот glvalue ссылается на объект, динамический тип которого неизвестен для constexpr(начиная с C++20)
  23. выражение new, если не выполняется одно из следующих условий:(начиная с C++20)
    • Выбранная функция выделения памяти является заменяемой глобальной функцией выделения памяти, а выделенное хранилище освобождается в ходе вычисления этого выражения.
    (начиная с C++20)
    • Выбранная функция выделения памяти является формой без выделения памяти с выделяемым типом T, а аргумент размещения удовлетворяет всем следующим условиям:
    • Он указывает на:
      • объект, тип которого подобен T, если T не является типом массива, или
      • первый элемент объекта типа, подобного T, если T является типом массива.
    • Он указывает на хранилище, длительность существования которого началась в ходе вычисления этого выражения.
    (начиная с C++26)
  24. выражение delete, если только оно не освобождает область хранилища, выделенную в ходе вычисления этого выражения(начиная с C++20)
  25. (начиная с C++20) сопрограммы: выражение await или выражение yield
  26. (начиная с C++20) трёхстороннее сравнение, если его результат не определён
  27. оператор равенства или отношения, результат которого не определён
  28. (до C++14) оператор присваивания или составного присваивания
  29. (до C++26) выражение throw
  30. (начиная с C++26) создание объекта исключения, если только объект исключения и все его неявные копии, созданные вызовами std::current_exception или std::rethrow_exception, не уничтожаются в ходе вычисления этого выражения
    constexpr void check(int i)
    {
        if (i < 0)
            throw i;
    }
    
    constexpr bool is_ok(int i)
    {
        try {
            check(i);
        } catch (...) {
            return false;
        }
        return true;
    }
    
    constexpr bool always_throw()
    {
        throw 12;
        return true;
    }
    
    static_assert(is_ok(5)); // OK
    static_assert(!is_ok(-1)); // OK since C++26
    static_assert(always_throw()); // Error: uncaught exception
    
  31. объявление asm
  32. вызов макроса va_arg
  33. оператор goto
  34. выражение dynamic_cast или typeid или выражение new(начиная с C++26), которое вызвало бы исключение при отсутствии достижимого определения типа исключения(начиная с C++26)
  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)

Дополнительные требования

Даже если выражение E не вычисляет ничего из перечисленного выше, вопрос о том, является ли E основным константным выражением, определяется реализацией, если вычисление E привело бы к неопределённому поведению во время выполнения из-за использования [[noreturn]] или [[assume]].

Даже если выражение E не вычисляет ничего из перечисленного выше, не указано, является ли E основным константным выражением, если вычисление E привело бы к вычислению чего-либо из следующего:

  • Операция со стандартной библиотекой, имеющая неопределённое поведение.
  • Вызов макроса va_start.

При определении того, является ли выражение основным константным выражением, вычисление тела функции-члена std::allocator<T> игнорируется, если T является литеральным типом.

При определении того, является ли выражение основным константным выражением, считается, что вызов тривиального конструктора копирования/перемещения или оператора присваивания копированием/перемещением объединения копирует/перемещает активный член объединения, если он есть.

При определении того, является ли выражение основным константным выражением, вычисление выражения-идентификатора, именующего структурированную привязку bd, выполняется следующим образом:

  • Если bd является lvalue, ссылающимся на объект, привязанный к вымышленной ссылке ref, поведение такое, как если бы был выбран ref.
  • В противном случае, если bd именует элемент массива, поведение соответствует вычислению e[i], где e — имя переменной, инициализированной из инициализатора объявления структурированной привязки, а i — индекс элемента, на который ссылается bd.
  • В противном случае, если bd именует член класса, поведение соответствует вычислению e.m, где e — имя переменной, инициализированной из инициализатора объявления структурированной привязки, а m — имя члена, на который ссылается bd.
(начиная с C++26)

Во время вычисления выражения как основного константного выражения все имена и использования *this, относящиеся к объекту или ссылке, время жизни которых началось вне вычисления выражения, рассматриваются как относящиеся к конкретному экземпляру этого объекта или ссылки, время жизни которого, а также всех подобъектов (включая все члены объединения), охватывает всё константное вычисление.

  • Для такого объекта, который не является доступным в константных выражениях(начиная с C++20) его динамический тип является неизвестным для constexpr.
  • Для такой ссылки, которая недоступна в константных выражениях(начиная с C++20) считается, что ссылка привязана к неопределённому объекту ссылочного типа, время жизни которого и всех подобъектов охватывает всё константное вычисление, а его динамический тип неизвестен для constexpr.

Целочисленное константное выражение

Целочисленное константное выражение — это выражение целочисленного типа или нескоупированного типа перечисления, неявно преобразованное к prvalue, причём преобразованное выражение является основным константным выражением.

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

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

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

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

Если при этом происходит привязка ссылки, она может быть только прямой привязкой.

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

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

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

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

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

Составляющие сущности

Неформально, составляющие значения и составляющие ссылки объекта obj — это скалярные значения и ссылки, из которых состоит obj.

Формально, составляющие значения объекта obj определяются следующим образом:

  • Если obj имеет скалярный тип, составляющим значением является значение obj.
  • В противном случае составляющими значениями являются составляющие значения любых прямых подобъектов obj, кроме неактивных членов объединения.

Составляющие ссылки объекта obj включают следующие ссылки:

  • любые прямые члены obj, имеющие ссылочный тип
  • составляющие ссылки любых прямых подобъектов obj, кроме неактивных членов объединения

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

  • Если var объявляет объект, составляющими значениями и ссылками являются составляющие значения и ссылки этого объекта.
  • Если var объявляет ссылку, составляющей ссылкой является эта ссылка.

Для любой составляющей ссылки ref переменной var, если ref привязана к временному объекту или его подобъекту, время жизни которого продлено до времени жизни ref, составляющие значения и ссылки этого временного объекта также являются составляющими значениями и ссылками var, рекурсивно.

Сущности, представимые в constexpr

Любой объект со статической длительностью хранения является доступным для ссылки в constexpr в любой точке программы.

Объект с автоматической длительностью хранения является доступным для ссылки в constexpr начиная с точки P, если объект определён в теле функции, непосредственно содержащем P.

void f() {
    constexpr int x = 42;
    constexpr const int& ref = x; // OK, x is constexpr-referenceable here

    const int& r = 42; // r binds to a temporary object with automatic storage duration
    constexpr const int& ref2 = r; // OK, the temporary is constexpr-referenceable here

    [&] {
        constexpr const int& ref = x; // Error: x is not constexpr-referenceable here
    };
}
(начиная с C++26)

Другие объекты (например, объекты с потоковой длительностью хранения) никогда не доступны для ссылки в constexpr.

Объект или ссылка x является представимым в constexpr в точке P, если выполняются все следующие условия:

  • Для каждого составляющего значения x, указывающего на объект obj, obj доступен для ссылки в constexpr из P.
  • Для каждого составляющего значения x, указывающего за пределы объекта obj, obj доступен для ссылки в constexpr из P.
  • Для каждой составляющей ссылки x, ссылающейся на объект obj, obj доступен для ссылки в constexpr из P.

Сущности с константной инициализацией

Переменная или временный объект obj имеет константную инициализацию, если выполняются все следующие условия:

  • У него есть инициализатор либо его тип является конструируемым по умолчанию для const.
  • Полное выражение его инициализации является константным выражением в контексте, требующем константного выражения, за исключением случая, когда obj является объектом: тогда это полное выражение также может вызывать конструкторы constexpr для obj и его подобъектов, даже если эти объекты имеют типы классов, не являющиеся литеральными.
(до C++26)

Переменная var является инициализируемой константным выражением, если выполняются все следующие условия:

  • Полное выражение её инициализации является константным выражением в контексте, требующем константного выражения, при этом все проверки контрактов используют семантику вычисления «ignore».
  • Сразу после инициализирующего объявления var объект или ссылка, объявленные var, представимы в constexpr.
  • Если объект или ссылка x, объявленные var, имеют статическую или потоковую длительность хранения, x представимы в constexpr в ближайшей точке, непосредственная область видимости которой является областью видимости пространства имён и которая следует за инициализирующим объявлением var.

Переменная, инициализируемая константным выражением, имеет константную инициализацию, если у неё есть инициализатор либо её тип является конструируемым по умолчанию для const.

void f() {
    int ax = 0; // ax is constant-initialized
    thread_local int tx = 0; // tx is constant-initialized
    static int sx; // sx is not constant-initialized (it doesn't have an initializer)

    constexpr int& raa = ax; // OK, raa is constant-initialized

    constexpr int& rat = tx;
    // Error: rat is not constant-initialized
    // because it's not constexpr-representable here

    static constexpr int& rsa = ax;
    // Error: rsa is not constant-initialized
    // because it's not constexpr-representable at the nearest namespace scope
}
(начиная с C++26)

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

Переменная является потенциально константной, если она является переменной constexpr либо имеет ссылочный тип или неквалифицированный volatile целочисленный тип или тип перечисления с квалификатором const.

Переменная var может использоваться в константных выражениях в точке P, если:

  • var имеет константную инициализацию,
  • var является потенциально константной,
  • инициализирующее объявление var D достижимо из P, и
  • выполняется одно из следующих условий:
    • var является переменной constexpr.
    • var не инициализирована значением, локальным для единицы трансляции.
    • P находится в той же единице трансляции, что и D.

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

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

Выражения с явным вычислением на этапе компиляции

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

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

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

(начиная с C++20)

Функции и переменные, необходимые для вычисления на этапе компиляции

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

  • выражения с явным вычислением на этапе компиляции
  • потенциально вычисляемые выражения
  • непосредственные подвыражения списка инициализаторов в фигурных скобках (для определения того, является ли преобразование сужающим, может потребоваться вычисление на этапе компиляции)
  • выражения взятия адреса, встречающиеся внутри шаблонной сущности (вычисление на этапе компиляции может потребоваться, чтобы определить, является ли такое выражение зависимым от значения)
  • подвыражения одного из перечисленных выше выражений, которые не являются подвыражениями вложенного невычисляемого операнда

Функция необходима для вычисления на этапе компиляции, если она является функцией constexpr и именуется выражением, потенциально вычисляемым на этапе компиляции.

Переменная необходима для вычисления на этапе компиляции, если она является переменной constexpr или имеет целочисленный тип с квалификатором const и без volatile либо ссылочный тип, а идентификаторное выражение, обозначающее её, потенциально вычисляется на этапе компиляции.

Определение функции с defaulted-реализацией и инстанцирование специализации шаблона функции или специализации шаблона переменной(начиная с C++14) выполняются, если функция или переменная(начиная с C++14) необходима для вычисления на этапе компиляции.

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

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

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

Примечания

Макрос проверки поддержки возможностей Значение Стандарт Возможность
__cpp_constexpr_in_decltype 201711L (C++20)
(DR11)
Создание определений функций и переменных, когда они необходимы для вычисления на этапе компиляции
__cpp_constexpr_dynamic_alloc 201907L (C++20) Операции с динамической длительностью хранения в функциях constexpr
__cpp_constexpr 202306L (C++26) Приведение constexpr из void*: на пути к стиранию типа constexpr
202406L (C++26) Размещение new и new[] с constexpr
__cpp_constexpr_exceptions 202411L (C++26) Исключения constexpr

Пример

Отчёты об ошибках

Следующие отчёты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.

DR Применён к Опубликованное поведение Корректное поведение
CWG 94 C++98 арифметические константные выражения не могли
содержать переменные и статические члены данных
могут
CWG 366 C++98 выражения со строковыми литералами
могли быть целочисленными константными выражениями
не могут
CWG 457 C++98 выражения с volatile-переменными
могли быть целочисленными константными выражениями
не могут
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 для объектов, которые можно использовать в константных выражениях,
их изменяемые подобъекты также можно было использовать
их использовать нельзя
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 1613 C++11 основные константные выражения могли вычислять любые
ODR-используемые ссылки внутри лямбда-выражений
некоторые ссылки
вычислять было нельзя
CWG 1694 C++11 привязка prvalue-указателя на локальную переменную к ссылке
была константным выражением
это не является
константным выражением
CWG 1872 C++11 основные константные выражения могли вызывать инстанцирования шаблонов функций constexpr,
не удовлетворяющие требованиям к функциям constexpr
такие инстанцирования
вызывать нельзя
CWG 1952 C++11 требовалось диагностировать неопределённое поведение
стандартной библиотеки
не определено, должна ли
выдаваться диагностика
CWG 2022 C++98 результат определения константного выражения мог
зависеть от выполнения устранения копирования
считать, что устранение копирования
выполняется всегда
CWG 2126 C++11 временные объекты литеральных типов с квалификатором const,
инициализированные константой и с продлённым временем жизни, нельзя было использовать в константных выражениях
можно использовать
CWG 2129 C++11 целочисленные литералы не являлись константными выражениями являются
CWG 2167 C++11 ссылки на нешаблонные сущности, локальные для вычисления,
делали вычисление не constexpr
ссылки на нешаблонные
сущности разрешены
CWG 2278 C++98 решение проблемы CWG 2022 нельзя было реализовать считать, что устранение копирования
никогда не выполняется
CWG 2299 C++14 не было ясно, можно ли использовать макросы из <cstdarg>
при вычислении на этапе компиляции
va_arg запрещён,
va_start не определено
CWG 2400 C++11 вызов виртуальной функции constexpr для объекта, который нельзя использовать
в константных выражениях и время жизни которого началось вне
выражения, содержащего вызов, мог быть константным выражением
это не является
константным выражением
CWG 2490 C++20 вызовы (псевдо)деструкторов не имели
ограничений при вычислении на этапе компиляции
добавлено ограничение
CWG 2552 C++23 при вычислении основного константного выражения поток
управления не мог проходить через объявление переменной вне блока
может
CWG 2558 C++11 неопределённое значение могло быть константным выражением не является константным выражением
CWG 2647 C++20 переменные volatile-квалифицированных типов могли быть потенциально константными не являются
CWG 2763 C++11 нарушение [[noreturn]] не требовалось
обнаруживать при вычислении на этапе компиляции
требуется
CWG 2851 C++11 преобразованные константные выражения
не допускали преобразований чисел с плавающей точкой
допускать не сужающие
преобразования чисел с плавающей точкой
CWG 2907 C++11 основные константные выражения не могли выполнять
преобразования lvalue-to-rvalue для glvalue std::nullptr_t
могут выполнять такие
преобразования
CWG 2909 C++20 переменная без инициализатора могла иметь константную инициализацию,
только если её инициализация по умолчанию
приводила к выполнению некоторой инициализации
константная инициализация возможна,
только если тип является
конструируемым по умолчанию для const
CWG 2924 C++11
C++23
не было определено, является ли выражение, нарушающее
ограничения [[noreturn]] (C++11) или
[[assume]] (C++23), основным константным выражением
определяется
реализацией
P2280R4 C++11 вычисление выражения, содержащего идентификаторное выражение
или *this, ссылающееся на объект или ссылку, время жизни
которых началось вне данного вычисления, не является константным выражением
может быть
константным выражением

См. также

спецификатор 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/cpp/language/constant_expression

Spec-Zone.ru

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