Открыть сервис

constexpr

constexpr — это спецификатор типа и ключевое слово языка программирования C++ (начиная со стандарта C++11), которое указывает компилятору, что значение переменной, возвращаемое значение функции или тело функции может быть вычислено на этапе компиляции. Использование constexpr позволяет перенести часть вычислений с этапа выполнения программы на этап её сборки, что повышает производительность и даёт возможность использовать результаты вычислений в контекстах, требующих константных выражений (например, при задании размеров массивов или в шаблонных аргументах).

История

Концепция вычислений на этапе компиляции существовала в C++ и ранее, но была ограничена. До C++11 единственным способом получить константное выражение было использование макросов препроцессора (#define) или литеральных констант. В C++11 был введён спецификатор constexpr, который позволил помечать функции и переменные, значения которых гарантированно вычисляются на этапе компиляции, если все аргументы являются константными выражениями.

В стандарте C++14 требования к constexpr были существенно ослаблены: разрешены локальные переменные, циклы, условные операторы и модификация объектов внутри constexpr-функций. C++17 расширил применение constexpr на лямбда-выражения и if constexpr (условная компиляция на этапе компиляции). В C++20 появились constexpr-виртуальные функции, constexpr-динамическая память (ограниченно), а также constexpr-блоки try и throw (в определённых условиях). C++23 продолжил расширение, добавив constexpr для некоторых стандартных библиотек и типов.

Синтаксис и семантика

Ключевое слово constexpr может применяться в трёх основных контекстах:

  1. Переменные: constexpr int size = 10; — объявляет константу, значение которой должно быть известно на этапе компиляции. Такая переменная неявно является const.
  2. Функции: constexpr int square(int x) { return x * x; } — объявляет функцию, которая может быть вычислена на этапе компиляции, если все её аргументы — константные выражения. Если аргументы не являются константными, функция выполняется во время выполнения обычным образом.
  3. Конструкторы и пользовательские литералы: constexpr-конструктор позволяет создавать объекты пользовательских типов на этапе компиляции. В C++20 также появилась возможность помечать constexpr функции, выделяющие память (с помощью new), при условии, что память освобождается в том же контексте компиляции.

Ограничения (на примере C++17)

Для constexpr-функций существуют строгие ограничения на тело функции:

  • Тело должно состоять из единственного return-выражения (в C++11) или может содержать локальные переменные, циклы, if, switch (начиная с C++14).
  • Запрещены: goto, try-блоки (кроме C++20), static и thread_local переменные, вызовы не-constexpr функций.
  • В C++17 и ранее запрещено выделение динамической памяти (new/delete) — это ограничение частично снято в C++20.

Примеры использования

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

```cpp constexpr int factorial(int n) { return (n <= 1) ? 1 : (n * factorial(n - 1)); }

int main() { constexpr int fact5 = factorial(5); // вычислено на этапе компиляции int arr[fact5]; // массив из 120 элементов — разрешено return 0; } ```

Использование if constexpr (C++17)

``cpp template <typename T> auto get_value(T t) { if constexpr (std::is_pointer_v<T>) { return *t; } else { return t; } } `` В этом примере ветка, не соответствующая типу, не компилируется — это позволяет писать обобщённый код без специализаций.

Пользовательский тип с constexpr-конструктором

```cpp class Point { public: constexpr Point(double x, double y) : x_(x), y_(y) {} constexpr double x() const { return x_; } constexpr double y() const { return y_; } private: double x_, y_; };

constexpr Point origin(0.0, 0.0); // объект создан на этапе компиляции ```

Отличия от const

Хотя оба ключевых слова связаны с неизменяемостью, они имеют разную семантику:

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

Переменная const может быть инициализирована значением, вычисляемым во время выполнения, в то время как constexpr-переменная — только константным выражением.

Применение

constexpr широко применяется в:

  • Метапрограммировании шаблонов: замена устаревших техник с использованием рекурсивных шаблонов и специализаций на более читаемый код с constexpr-функциями.
  • Оптимизации производительности: вычисление сложных констант (таблиц, хешей, конфигураций) на этапе компиляции.
  • Библиотеках и фреймворках: например, в <ratio> (рациональные числа времени компиляции), <chrono> (константы времени), <type_traits> (проверки типов).
  • Разработке встраиваемых систем: где критична предсказуемость времени выполнения и минимизация кода.

Критика и ограничения

Несмотря на полезность, constexpr имеет ряд недостатков:

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

Влияние на другие языки

Идея вычислений на этапе компиляции не уникальна для C++. Похожие механизмы существуют в:

  • Rust: ключевое слово const для констант и const fn для функций, вычисляемых на этапе компиляции.
  • D: ключевое слово immutable и enum с константными выражениями.
  • Zig: ключевое слово comptime, позволяющее выполнять произвольный код на этапе компиляции.
  • C: в стандарте C23 введён спецификатор constexpr для переменных (аналогично C++), но не для функций.

Источники

  1. Международный стандарт ISO/IEC 14882:2020 (C++20) — разделы 7.7 «Constant expressions», 9.2.5 «The constexpr specifier».
  2. Vandevoorde, D., Josuttis, N., & Gregor, D. (2017). C++ Templates: The Complete Guide. 2nd ed. Addison-Wesley.
  3. Stroustrup, B. (2013). The C++ Programming Language. 4th ed. Addison-Wesley.
  4. Документация cppreference.com — статья «constexpr specifier».

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →