const, constexpr и consteval в C++: где граница
Три ключевых слова про неизменяемость и вычисления на этапе компиляции легко путают. Разбираем, чем они отличаются, где каждое ломается и что реально считается до запуска программы.

В код-ревью на C++ регулярно всплывает один и тот же спор: «Здесь надо constexpr, а не const» — «Нет, тут вообще consteval». За тремя похожими словами стоят разные механизмы. const — это про запрет изменения. constexpr — про то, что значение можно вычислить на этапе компиляции. consteval — про то, что оно обязано быть вычислено на этапе компиляции. Смешивать их — верный способ получить либо ложное ощущение оптимизации, либо ошибку компиляции там, где её не ждёшь.
Разбор ведётся на C++20 и новее: consteval появился именно в C++20, а правила constexpr заметно расширялись от версии к версии. Где поведение зависит от стандарта — это указано отдельно. Тема неизменяемости в многопоточности (const и потокобезопасность) здесь не рассматривается — это отдельный разговор.
const: запрет на изменение, и только
const говорит одно: через это имя объект менять нельзя. Ничего про этап компиляции здесь нет. Значение может стать известно и в рантайме.
int get_from_config(); // читает файл при запуске
const int max_users = 100; // известно при компиляции
const int limit = get_from_config(); // известно только в рантайме, но менять нельзяОба const. Второй нельзя использовать там, где нужна константа времени компиляции — например, как размер массива на стеке. Компилятор про значение limit ничего не знает до запуска.
Отдельная ловушка — const у указателей. Позиция слова относительно звёздочки меняет смысл:
const int* p— нельзя менять то, на что указываетp, сам указатель менять можно;int* const p— нельзя переприсваивать сам указатель, значение по адресу менять можно;const int* const p— нельзя ни то, ни другое.
Читается справа налево от имени. Путаница здесь — источник половины непонятных ошибок компиляции у тех, кто пришёл из языков без указателей.
constexpr: можно посчитать до запуска
constexpr у переменной значит: значение обязано быть вычислимо на этапе компиляции. Компилятор проверит это и откажется собирать код, если вычислить нельзя.
constexpr int side = 4;
constexpr int area = side * side; // посчитано компилятором, area == 16
int runtime_val = get_from_config();
// constexpr int bad = runtime_val; // ошибка: значение не известно при компиляцииС функциями сложнее. constexpr-функция может быть выполнена на этапе компиляции, но не обязана. Всё зависит от контекста вызова.
constexpr int square(int n) { return n * n; }
constexpr int a = square(5); // компиляция: a == 25 на этапе сборки
int x = get_from_config();
int b = square(x); // рантайм: x неизвестен, считается при запускеОдна и та же функция сработала двумя способами. Если хотя бы один путь вычисления функции невозможен на этапе компиляции (например, внутри есть чтение файла), функция вообще не может быть constexpr — компилятор это отвергнет. Но если все ветки в принципе пригодны, конкретный вызов уходит в рантайм, когда аргумент неизвестен заранее.
Ключевая мысль: constexpr у функции — это разрешение, а не приказ. Компилятор посчитает на этапе сборки, только если контекст этого требует и все аргументы известны.Что можно внутри constexpr-функции
Правила расширялись. В C++11 тело было почти пустым — по сути один return. В C++14 разрешили циклы и локальные переменные. В C++20 внутри constexpr стали допустимы try/catch, изменения объединений и, что важнее, выделение памяти через new/delete при условии, что она освобождена в пределах того же вычисления времени компиляции. Именно поэтому в C++20 появились constexpr-версии std::vector и std::string. Если целитесь в компиляторы постарше — сверяйтесь с таблицей поддержки на cppreference, здесь много подводных версионных различий.
consteval: обязано считаться при компиляции
consteval объявляет immediate function — «немедленную функцию». Любой её вызов обязан быть вычислен на этапе компиляции. Если вычислить нельзя — ошибка сборки, а не откат в рантайм.
consteval int square_now(int n) { return n * n; }
constexpr int a = square_now(5); // ок, посчитано при сборке
int x = get_from_config();
// int b = square_now(x); // ошибка: x неизвестен при компиляцииРазница с constexpr в одной строке: там, где constexpr square(x) тихо ушёл бы в рантайм, consteval square_now(x) роняет компиляцию. Это ровно то, что нужно, когда вычисление обязано исчезнуть до запуска — например, разбор формата строки или проверка литерала.
Чем это отличается на практике
| Свойство | const | constexpr | consteval |
|---|---|---|---|
| Запрещает изменение | да | да (переменная неизменна) | — |
| Значение известно при компиляции | не обязательно | переменная — да; функция — если контекст требует | функция — всегда |
| Применяется к функциям | методы (const-метод) | да | да |
| Откат в рантайм | — | возможен для функции | запрещён |
| С какого стандарта | C++98 | C++11, расширения в 14/20 | C++20 |
Когда что выбирать
Критерий простой — от того, что вы хотите гарантировать:
- Нужно запретить изменение и всё —
const. Параметры функций, методы, ссылки только для чтения. - Значение известно заранее и хочется, чтобы оно могло попасть в вычисления времени компиляции (размер массива, шаблонный аргумент,
enum-подобная таблица) —constexpr. - Вычисление обязано произойти до запуска и попадание в рантайм — это ошибка —
consteval. Типичный случай: фабрики значений, где рантайм-вызов не имеет смысла и его нужно запретить на уровне компилятора.
Ещё один родственник — constinit (тоже C++20). Он не про неизменяемость и не про вычисления в общем смысле: гарантирует, что переменная со статическим временем жизни инициализируется на этапе компиляции, чтобы избежать проблемы порядка инициализации глобальных объектов. При этом менять её потом можно. Не путайте с constexpr-переменной, которая ещё и неизменна.
Где всё ломается
const не даёт значения времени компиляции. Классическая ошибка новичка: объявил const int n = f(); и удивляется, почему int arr[n]; не собирается со строгими настройками. Нужен constexpr.
constexpr-функция молча ушла в рантайм. Вы думали, что тяжёлое вычисление исчезло на этапе сборки, а оно считается при каждом запуске, потому что аргумент оказался не-константным. Компилятор при этом не жалуется. Если вам нужна гарантия — либо присвойте результат constexpr-переменной (тогда рантайм-путь станет ошибкой), либо используйте consteval.
consteval и передача через обычную функцию. Нельзя взять адрес immediate-функции как обычный указатель на функцию и передать её туда, где она вызовется в рантайме. Также аккуратно с шаблонами: параметр, приходящий из рантайма, «отравляет» цепочку — вызов consteval с ним не пройдёт.
Раздутое время компиляции. Тяжёлые вычисления, честно перенесённые на этап сборки, замедляют компиляцию — иногда до неприятных величин. Если сборка проекта стала заметно дольше после перевода логики в constexpr/consteval, это ожидаемая цена, а не баг. Мерьте время компиляции модуля до и после.
Что делать дальше
Пройдитесь по своим заголовочным файлам и разделите const-константы на две группы: те, что известны при сборке, переведите в constexpr — это открывает их для шаблонов и размеров массивов. Для функций, где вычисление в рантайме бессмысленно, попробуйте consteval и посмотрите, не всплывут ли неожиданные вызовы, которые компилятор теперь отвергает — часто это находит реальные ошибки логики. После перевода замерьте время компиляции затронутых единиц трансляции. За деталями по версиям — раздел о constexpr и consteval на cppreference: там указано, что появилось в каком стандарте.
Аналитик звонков: Разбор записи звонка по этапам сделки: где сорвалось, что упущено, три исправляемые рекомендации с цитатами.
Спросить за 90 ₽Источники: constexpr specifier — cppreference.com, consteval specifier — cppreference.com
Частые вопросы
А если у меня компилятор с поддержкой только C++17?
Почему constexpr-функция считается в рантайме, хотя я хотел на этапе сборки?
В чём разница между const-методом и constexpr-методом?
Можно ли передать consteval-функцию как аргумент-callback?
Не сработало: constexpr int arr_size = vec.size(); выдаёт ошибку. Почему?
Стоит ли переводить всё подряд в constexpr ради оптимизации?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.