Spec-Zone.ru › C++

constexpr спецификатор (начиная с C++11)

  • constexpr — указывает, что значение переменной, структурированная привязка(начиная с C++26) или функции может использоваться в константных выражениях

Пояснение

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

Спецификатор constexpr, используемый в объявлении объекта или нестатической функции-члена(до C++14), подразумевает const.

Спецификатор constexpr, используемый в первом объявлении функции или статического члена-данных(начиная с C++17), подразумевает inline. Если какое-либо объявление функции или шаблона функции содержит спецификатор constexpr, то каждое объявление должно содержать этот спецификатор.

Переменная constexpr

Переменную или шаблон переменной(начиная с C++14) можно объявить как constexpr, если выполнены все следующие условия:

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

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

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

Функция constexpr

Функцию или шаблон функции можно объявить как constexpr.

Функция является подходящей для constexpr, если выполнены все следующие условия:

  • Она не является виртуальной функцией.
(до C++20)
  • Ее возвращаемый тип (если он есть) является литеральным типом.
  • Каждый тип ее параметра является литеральным типом.
(до C++23)
  • Если это конструктор или деструктор(начиная с C++20), должны быть выполнены дополнительные условия (см. ниже).
(до C++26)
  • Она не является корутиной.
(начиная с C++20)
  • Тело функции представляет собой = default, = delete или составной оператор, который содержит только следующее:
    • пустые операторы
    • объявления static_assert
    • объявления typedef и объявления псевдонимов, которые не определяют классы или перечисления
    • объявления using
    • директивы using
    • ровно один оператор return, если функция не является конструктором
(до C++14)
  • Тело функции представляет собой = default, = delete или составной оператор, который(до C++20) не содержит следующее:
    • операторы goto
    • операторы с метками, отличными от case и default
  • блоки try
  • объявления встроенного ассемблера
  • определения переменных, для которых инициализация не выполняется
(до C++20)
  • определения переменных нелитеральных типов
  • определения переменных со статической длительностью хранения или длительностью хранения потока
(начиная с C++14)
(до C++23)

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

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

Если для шаблонной функции constexpr ни одна специализация шаблона функции/класса не сделает эту шаблонную функцию подходящей для constexpr при рассмотрении ее как нетемплейтной функции, программа некорректна, диагностика не требуется.

(до C++23)

Вызов функции constexpr в заданном контексте во всех отношениях дает тот же результат, что и вызов эквивалентной функции, не являющейся constexpr, в том же контексте, за следующими исключениями:

  • Вызов функции constexpr может использоваться в константном выражении.
  • Устранение копирования не выполняется в константном выражении.

Конструктор constexpr

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

  • Тело функции представляет собой = delete либо удовлетворяет следующим дополнительным требованиям:
  • Если класс является объединением с вариантными членами, инициализируется ровно один из них.
  • Если класс является подобным объединению классом, но не является объединением, то для каждого его анонимного члена-объединения с вариантными членами инициализируется ровно один из них.
  • Инициализируется каждый нестатический член-данных, не являющийся вариантным, и подобъект базового класса.
(до C++20)
  • Если конструктор является делегирующим конструктором, целевой конструктор является конструктором constexpr.
  • Если конструктор не является делегирующим, каждый конструктор, выбранный для инициализации нестатических членов-данных и подобъектов базовых классов, является конструктором constexpr.
(до C++23)
  • У класса нет ни одного виртуального базового класса.
(до C++26)

Конструкторам не нужно удовлетворять каким-либо дополнительным условиям, чтобы быть подходящими для constexpr.

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

Если для конструктора constexpr, не заданного по умолчанию и не являющегося шаблонным, не существует значений аргументов, при которых вызов функции мог бы быть вычисляемым подвыражением полного выражения инициализации какого-либо объекта, подпадающего под действие константного выражения, программа некорректна, диагностика не требуется.

(до C++23)

Деструктор constexpr

Деструкторы не могут быть constexpr, однако тривиальный деструктор может неявно вызываться в константных выражениях.

(до C++20)

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

  • Для каждого подобъекта типа класса или массива такого типа (возможно, многомерного) этот тип класса имеет деструктор constexpr.
(до C++23)
  • У класса нет ни одного виртуального базового класса.
(начиная с C++20)
(до C++26)

Деструкторам не нужно удовлетворять каким-либо дополнительным условиям, чтобы быть подходящими для constexpr.

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

Примечания

Поскольку оператор noexcept всегда возвращает true для константного выражения, его можно использовать, чтобы проверить, выбирает ли конкретный вызов функции constexpr ветвь константного выражения:

constexpr int f(); 
constexpr bool b1 = noexcept(f()); // false, undefined constexpr function
constexpr int f() { return 0; }
constexpr bool b2 = noexcept(f()); // true, f() is a constant expression
(до C++17)

Можно написать функцию constexpr, вызов которой никогда не сможет удовлетворить требованиям базового константного выражения:

void f(int& i) // not a constexpr function
{
    i = 0;
}

constexpr void g(int& i) // well-formed since C++23
{
    f(i); // unconditionally calls f, cannot be a constant expression
}
(начиная с C++23)

Конструкторы constexpr допустимы для классов, не являющихся литеральными типами. Например, конструктор по умолчанию для std::shared_ptr является constexpr, что позволяет выполнять константную инициализацию.

Переменные-ссылки можно объявлять как constexpr (их инициализаторы должны быть константными выражениями для ссылок):

static constexpr int const& x = 42; // constexpr reference to a const int object
                                    // (the object has static storage duration
                                    //  due to life extension by a static reference)

Хотя блоки try и встроенный ассемблер разрешены в функциях constexpr, выбрасывание исключений , которые не перехватываются(начиная с C++26) или выполнение ассемблерного кода по-прежнему запрещено в константном выражении.

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

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

(начиная с C++20)
Макрос проверки поддержки возможности Значение Стандарт Возможность
__cpp_constexpr 200704L (C++11) constexpr
201304L (C++14) Смягчение ограничений constexpr, методы constexpr, не являющиеся const
201603L (C++17) Лямбда-выражение constexpr
201907L (C++20) Тривиальная инициализация по умолчанию и объявление asm в функциях constexpr
202002L (C++20) Изменение активного члена объединения при константном вычислении
202110L (C++23) Переменные, не являющиеся литеральными, метки и операторы goto в функциях constexpr
202207L (C++23) Смягчение некоторых ограничений constexpr
202211L (C++23) Разрешение переменных static constexpr в функциях constexpr
202306L (C++26) Преобразование constexpr из void*: путь к стиранию типа constexpr
__cpp_constexpr_in_decltype 201711L (C++11)
(DR)
Создание определений функций и переменных, когда они необходимы для константного вычисления
__cpp_constexpr_dynamic_alloc 201907L (C++20) Операции с динамической длительностью хранения в функциях constexpr
__cpp_constexpr_virtual_inheritance 202506L (C++26) Виртуальное наследование constexpr

Ключевые слова

constexpr

Пример

Определяет функции constexpr C++11/14 для вычисления факториалов; определяет литеральный тип, расширяющий строковые литералы:

#include <iostream>
#include <stdexcept>

// C++11 constexpr functions use recursion rather than iteration
constexpr int factorial(int n)
{
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

// C++14 constexpr functions may use local variables and loops
#if __cplusplus >= 201402L
constexpr int factorial_cxx14(int n)
{
    int res = 1;
    while (n > 1)
        res *= n--;
    return res;
}
#endif // C++14

// A literal class
class conststr
{
    const char* p;
    std::size_t sz;
public:
    template<std::size_t N>
    constexpr conststr(const char(&a)[N]): p(a), sz(N - 1) {}
    
    // constexpr functions signal errors by throwing exceptions
    // in C++11, they must do so from the conditional operator ?:
    constexpr char operator[](std::size_t n) const
    {
        return n < sz ? p[n] : throw std::out_of_range("");
    }
    
    constexpr std::size_t size() const { return sz; }
};

// C++11 constexpr functions had to put everything in a single return statement
// (C++14 does not have that requirement)
constexpr std::size_t countlower(conststr s, std::size_t n = 0,
                                             std::size_t c = 0)
{
    return n == s.size() ? c :
        'a' <= s[n] && s[n] <= 'z' ? countlower(s, n + 1, c + 1)
                                   : countlower(s, n + 1, c);
}

// An output function that requires a compile-time constant, for testing
template<int n>
struct constN
{
    constN() { std::cout << n << '\n'; }
};

int main()
{
    std::cout << "4! = ";
    constN<factorial(4)> out1; // computed at compile time
    
    volatile int k = 8; // disallow optimization using volatile
    std::cout << k << "! = " << factorial(k) << '\n'; // computed at run time
    
    std::cout << "The number of lowercase letters in \"Hello, world!\" is ";
    constN<countlower("Hello, world!")> out2; // implicitly converted to conststr
    
    constexpr int a[12] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
    constexpr int length_a = sizeof a / sizeof(int); // std::size(a) in C++17,
                                                      // std::ssize(a) in C++20
    std::cout << "Array of length " << length_a << " has elements: ";
    for (int i = 0; i < length_a; ++i)
        std::cout << a[i] << ' ';
    std::cout << '\n';
}

Вывод:

4! = 24
8! = 40320
The number of lowercase letters in "Hello, world!" is 9
Array of length 12 has elements: 0 1 2 3 4 5 6 7 8 0 0 0

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

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

DR Применен к Поведение согласно опубликованной редакции Корректное поведение
CWG 1358 C++11 Для шаблонных функций constexpr также требовалось
наличие хотя бы одного допустимого значения аргумента
не требуется
CWG 1359 C++11 Конструкторы объединений constexpr
должны инициализировать все члены-данные
инициализируется ровно один член-данных
для непустых объединений
CWG 1366 C++11 классы с конструкторами constexpr, тела функций которых
представляют собой = default или = delete, могли иметь виртуальные базовые классы
такие классы не могут иметь
виртуальных базовых классов
CWG 1595 C++11 делегирующие конструкторы constexpr требовали,
чтобы все задействованные конструкторы были constexpr
требуется, чтобы constexpr был только
целевой конструктор
CWG 1712 C++14 требовалось, чтобы все объявления шаблона переменной constexpr
содержали спецификатор constexpr[1]
больше не требуется
CWG 1911 C++11 конструкторы constexpr для нелитеральных типов не допускались допускаются при константной инициализации
CWG 2004 C++11 копирование/перемещение объединения с изменяемым членом
допускалось в константном выражении
изменяемые варианты запрещают
неявное копирование/перемещение
CWG 2022 C++98 вопрос о том, дают ли эквивалентные функции constexpr и не-constexpr
одинаковый результат, мог зависеть
от того, выполняется ли устранение копирования
считать, что в константных выражениях устранение
копирования всегда выполняется
CWG 2163 C++14 метки допускались в функциях constexpr,
хотя операторы goto запрещены
метки также запрещены
CWG 2268 C++11 копирование/перемещение объединения с изменяемым членом было
запрещено решением CWG issue 2004
допускается, если объект создан
внутри константного выражения
CWG 2278 C++98 решение CWG issue 2022 невозможно было реализовать считать, что в константных выражениях устранение
копирования никогда не выполняется
CWG 2531 C++11 не встроенная переменная становилась встроенной,
если ее повторно объявляли с constexpr
переменная не становится
встроенной
  1. Это избыточно, поскольку не может существовать более одного объявления шаблона переменной со спецификатором constexpr.

См. также

константное выражение определяет выражение, которое можно вычислить во время компиляции
consteval спецификатор(C++20) указывает, что функция является немедленной функцией, то есть каждый вызов функции должен выполняться при константном вычислении
constinit спецификатор(C++20) утверждает, что переменная имеет статическую инициализацию, то есть инициализацию нулем и константную инициализацию
Документация по C для constexpr

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/constexpr

Spec-Zone.ru

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