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 может применяться в трёх основных контекстах:
- Переменные:
constexpr int size = 10;— объявляет константу, значение которой должно быть известно на этапе компиляции. Такая переменная неявно являетсяconst. - Функции:
constexpr int square(int x) { return x * x; }— объявляет функцию, которая может быть вычислена на этапе компиляции, если все её аргументы — константные выражения. Если аргументы не являются константными, функция выполняется во время выполнения обычным образом. - Конструкторы и пользовательские литералы:
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++), но не для функций.
Источники
- Международный стандарт ISO/IEC 14882:2020 (C++20) — разделы 7.7 «Constant expressions», 9.2.5 «The constexpr specifier».
- Vandevoorde, D., Josuttis, N., & Gregor, D. (2017). C++ Templates: The Complete Guide. 2nd ed. Addison-Wesley.
- Stroustrup, B. (2013). The C++ Programming Language. 4th ed. Addison-Wesley.
- Документация cppreference.com — статья «constexpr specifier».
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →