Spec-Zone.ru › C++

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

  • constexpr - указывает, что значение переменной или функции может использоваться в константных выражениях

Объяснение

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

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

Переменная constexpr

Переменная constexpr должна удовлетворять следующим требованиям:

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

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

(с C++20)

Функция constexpr

Функция constexpr должна удовлетворять следующим требованиям:

  • она не должна быть виртуальной
  • она не должна быть функцией с блоком обработки исключений
(до C++20)
  • она не должна быть корутиной
(с C++20)
  • для конструктора и деструктора(с C++20) класс не должен иметь виртуальных базовых классов
  • её возвращаемое значение (если есть) и каждый из её параметров должны быть типа LiteralType
  • существует по крайней мере один набор значений аргументов, при котором вызов функции может быть вычисленным подвыражением ядра константного выражения (для конструкторов, использование в константном инициализаторе достаточно). Диагностика нарушения этого пункта не требуется.
(до C++23)
  • тело функции должно быть либо удалено, либо по умолчанию, или содержать только следующее:
    • пустые операторы (простые точки с запятой)
    • объявления static_assert
    • объявления typedef и объявления алиасов, не определяющие классы или перечисления
    • используемые объявления
    • используемые директивы
    • если функция не является конструктором, ровно одно возвращающееся утверждение
(до C++14)
  • тело функции не должно содержать:
    • оператор goto
    • оператор с меткой, отличной от case и default
  • блок try-catch
  • объявление asm
  • определение переменной, для которой не выполняется инициализация
(до C++20)
  • определение переменной нелитерального типа
  • определение переменной со статическим или потоковым временем жизни
(Тело функции, которое = default; или = delete; не содержит ничего из вышеперечисленного.)
(с C++14)
(до C++23)

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

Конструктор constexpr, тело которого не = delete; , должен удовлетворять следующим дополнительным требованиям:

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

Деструктор constexpr

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

(до C++20)

Деструктор constexpr, тело которого не = delete; , должен удовлетворять следующему дополнительному требованию:

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

Для шаблонов функций constexpr и constexpr-членов-функций шаблонов классов по крайней мере одна специализация должна удовлетворять вышеуказанным требованиям. Другие специализации всё ещё считаются constexpr, даже если вызов такой функции не может появиться в константном выражении. Если ни одна специализация шаблона не удовлетворяла бы требованиям к constexpr-функции, если рассматривать её как нешаблонную функцию, шаблон некорректен; диагностика не требуется.(до C++23)

Примечания

Поскольку оператор 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::unique_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)
END_OF_DOCUMENT_MARKER

Несмотря на то, что блоки try и встроенный ассемблер разрешены в функциях constexpr, выброс исключений или выполнение ассемблера всё ещё запрещено в константном выражении.

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

(с C++20)
Макросы проверки функций Значение Стандарт Функция
__cpp_constexpr 200704L (C++11) constexpr
201304L (C++14) Смягчённые constexpr, методы constexpr без const
201603L (C++17) Функция лямбда constexpr
201907L (C++20) Тривиальная инициализация по умолчанию и ассемблерные инструкции в функциях constexpr
202002L (C++20) Изменение активного члена объединения в константном вычислении
202110L (C++23) Переменные, не являющиеся литералами, метки и goto операторы в функциях constexpr
202207L (C++23) Смягчение некоторых ограничений constexpr
202211L (C++23) Разрешение статических constexpr переменных в функциях constexpr
202306L (C++26) constexpr приведение типов из void*: в сторону constexpr стирания типов
__cpp_constexpr_in_decltype 201711L (C++11)
(DR)
Генерация определений функций и переменных при необходимости для константного вычисления
__cpp_constexpr_dynamic_alloc 201907L (C++20) Операции для динамического объёма памяти в функциях 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 1712 C++14 шаблон переменной constexpr должен был содержать все
его объявления со спецификатором constexpr
(это избыточно, так как не может быть более одного
объявления шаблона переменной со спецификатором constexpr)
больше не требуется
CWG 1911 C++11 конструкторы constexpr для типов, не являющихся литералами, не были разрешены разрешены при константной инициализации
CWG 2004 C++11 копирование/перемещение объединения с изменяемым членом
было разрешено в константном выражении
изменяемые члены делают неявное копирование/перемещение недопустимым
CWG 2163 C++14 метки были разрешены в функциях constexpr
несмотря на запрет операторов goto
метки также запрещены
CWG 2268 C++11 копирование/перемещение объединения с изменяемым членом было
запрещено решением проблемы CWG 2004
разрешено, если объект создаётся
в рамках константного выражения

См. также

константное выражение определяет выражение, которое может быть вычислено во время компиляции
спецификатор 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/w/cpp/language/constexpr

Spec-Zone.ru

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