шаблоны и метапрограммированиеtemplates and metaprogramming
const — обещание не менять значение. constexpr — обещание, что значение МОЖНО вычислить на этапе компиляции (но не обязательно будет — компилятор решает по контексту). consteval (C++20) убирает эту неопределённость: функция обязана выполняться исключительно на этапе компиляции, иначе это ошибка.const is a promise not to change a value. constexpr is a promise that a value CAN be computed at compile time (but won't necessarily be — the compiler decides based on context). consteval (C++20) removes that ambiguity: the function must run exclusively at compile time, or it's an error.
const — свойство ПЕРЕМЕННОЙ: она не изменится после инициализации, но сама инициализация может происходить в рантайме (например, из результата вызова функции или пользовательского ввода). constexpr — требование, что инициализирующее выражение вычислимо на этапе компиляции; как следствие, constexpr-переменная автоматически const, но не наоборот.const is a property of a VARIABLE: it won't change after initialization, but the initialization itself may happen at runtime (e.g., from a function call result or user input). constexpr requires that the initializing expression be computable at compile time; as a consequence, a constexpr variable is automatically const, but not the other way around.
int getRuntimeValue();
const int a = getRuntimeValue(); // OK: const, но значение известно только в рантайме// OK: const, but the value is only known at runtime
constexpr int b = getRuntimeValue(); // ошибка компиляции: getRuntimeValue() не constexpr-функция// compile error: getRuntimeValue() isn't a constexpr function
constexpr int c = 2 + 2; // OK: вычислено компилятором, обычно даже без -O0// OK: computed by the compiler, usually even without -O0
constexpr на функции означает "эта функция МОЖЕТ быть вызвана в контексте, требующем константу времени компиляции, если все её аргументы тоже вычислимы на этапе компиляции". Если функция вызывается с аргументами, известными только в рантайме, тело выполняется в рантайме как обычная функция — никакой ошибки, просто отложенное вычисление.constexpr on a function means "this function MAY be called in a context requiring a compile-time constant, provided all its arguments are also compile-time computable." If the function is called with runtime-only arguments, its body simply runs at runtime like an ordinary function — no error, just deferred evaluation.
constexpr int square(int x) { return x * x; }
constexpr int a = square(5); // вычислено на этапе компиляции — 25 попадает прямо в бинарник// computed at compile time — 25 goes straight into the binary
int n;
std::cin >> n;
int b = square(n); // n известен только в рантайме — square выполняется как обычная функция// n is only known at runtime — square runs as an ordinary function
int arr[square(4)]; // требует константу времени компиляции — square(4) обязана вычислиться в компиляции// requires a compile-time constant — square(4) must be evaluated at compile time
Ограничения на тело constexpr-функции сильно ослаблены с C++11 до C++20: изначально допускался только один return-оператор; с C++14 разрешены циклы, локальные переменные, несколько операторов; с C++20 — даже try/catch (но не выброс исключения при реальном compile-time вычислении) и аллокации через new/delete, если они не переживают constant evaluation.The restrictions on a constexpr function's body loosened significantly from C++11 to C++20: originally only a single return statement was allowed; C++14 permits loops, locals, multiple statements; C++20 even allows try/catch (though not actually throwing during compile-time evaluation) and new/delete allocations, as long as they don't outlive the constant evaluation.
Функция, объявленная consteval ("immediate function"), обязана вычисляться исключительно на этапе компиляции — вызов с аргументом, не являющимся константой времени компиляции, это ошибка компиляции, а не отложенное вычисление в рантайме, как у constexpr.A function declared consteval (an "immediate function") must be evaluated exclusively at compile time — calling it with an argument that isn't a compile-time constant is a compile error, not a deferred runtime evaluation like with constexpr.
consteval int squareStrict(int x) { return x * x; }
constexpr int a = squareStrict(5); // OK// OK
int n;
std::cin >> n;
int b = squareStrict(n); // ошибка компиляции: n не константа времени компиляции// compile error: n isn't a compile-time constant
Практический смысл: consteval нужен, когда компиляция без вычисления на этапе компиляции бессмысленна или недопустима (например, генерация хеша строки-литерала для дальнейшего switch, парсинг формата на этапе компиляции, гарантия отсутствия рантайм-издержек в критическом месте) — constexpr в этих случаях допустил бы "тихий" откат в рантайм-выполнение, если что-то в вызывающем коде не является константой, что могло бы остаться незамеченным.The practical point: consteval is needed when compiling without compile-time evaluation would be meaningless or unacceptable (e.g., hashing a string literal for a later switch, compile-time format-string parsing, guaranteeing zero runtime cost at a critical spot) — constexpr in these cases would silently fall back to runtime execution if something in the caller isn't actually a constant, which could go unnoticed.
if constexpr (C++17) вычисляет условие на этапе компиляции и отбрасывает недостижимую ветку целиком — она даже не проверяется на корректность для данного набора типов (в отличие от обычного if, где обе ветки должны компилироваться для всех инстанциаций). Это заменяет собой большую часть случаев, где раньше требовался tag dispatch или частичная специализация.if constexpr (C++17) evaluates its condition at compile time and discards the unreachable branch entirely — it isn't even checked for validity against the given types (unlike a plain if, where both branches must compile for every instantiation). This replaces most cases that used to require tag dispatch or partial specialization.
template<typename T>
auto getValue(T x) {
if constexpr (std::is_pointer_v<T>)
return *x; // эта ветка не инстанцируется для не-указателей — не проверяется на корректность// this branch isn't instantiated for non-pointers — never checked for validity
else
return x;
}
constexpr int x = square(5); компилятор МОЖЕТ вычислить в рантайме, если это выгоднее по каким-то причинам оптимизации (на практике редко, но формально стандарт этого не гарантирует вне контекстов, реально требующих константу — массив фиксированного размера, static_assert, шаблонный нетиповой параметр). Если гарантия важна — использовать consteval или проверять сгенерированный код.constexpr int x = square(5); MAY still be evaluated by the compiler at runtime if that's advantageous for some optimization reason (rare in practice, but the standard doesn't formally guarantee otherwise outside contexts that genuinely require a constant — a fixed-size array, a static_assert, a non-type template parameter). If the guarantee matters, use consteval or inspect the generated code.
constexpr-метод может вызываться и в рантайме на обычном (не constexpr) объекте — это просто ещё один "может, но не обязан" контекст, а не требование, что объект целиком существует только на этапе компиляции.A constexpr method can also be called at runtime on an ordinary (non-constexpr) object — it's just another "may, but isn't required to" context, not a requirement that the object exists only at compile time.
Поскольку consteval-функция обязана исчезать после компиляции (не оставляет вызываемого рантайм-кода в общем случае), взятие её адреса для последующего вызова через указатель на функцию — ошибка компиляции. Это отличает её от constexpr-функции, у которой всегда есть полноценная рантайм-версия на всякий случай.Since a consteval function is required to vanish after compilation (it generally leaves behind no callable runtime code), taking its address for a later call through a function pointer is a compile error. This distinguishes it from a constexpr function, which always keeps a full runtime version around just in case.