constexpr and compile-time programming
Understand constexpr evaluation, consteval, constinit, and static_assert with practical examples.
Learning objectives
- Distinguish const objects from constant expressions.
- Write a constexpr function usable at compile time and runtime.
- Understand consteval and constinit.
- Use static_assert to check assumptions at compile time.
Prerequisites: functions, constants, templates, and basic type deduction. Review Functions, Constants, and Templates first. The examples distinguish C++11, C++17, and C++20 facilities.
Language versions: constexpr and static_assert are available from C++11; C++14 relaxed the rules for constexpr function bodies. The message-free form of static_assert used below requires C++17. consteval and constinit are C++20 features.
const is not the same as constexpr
const means an object cannot be modified through that name after initialization. constexpr says a variable or function can participate in constant evaluation when the rules and arguments permit it.
constexpr int square(int value) {
return value * value;
}
constexpr int answer = square(6); // evaluated at compile time
int runtime_value = 7;
int result = square(runtime_value); // evaluated at runtimeA constexpr function is not forced to run at compile time on every call. It can also execute at runtime when given runtime values.
Compile-time checks with static_assert
static_assert(sizeof(int) >= 2, "int must be at least 2 bytes"); static_assert(square(4) == 16);
If the condition is false, compilation fails. Use this for assumptions that must be true for the program to be valid.
consteval: must be compile time
consteval int triple(int value) {
return value * 3;
}
constexpr int fixed = triple(5); // valid
// int x = triple(runtime_value); // invalidA consteval function is an immediate function: calls must be constant-evaluated. This is stricter than constexpr.
constinit: static initialization guarantee
constinit int global_counter = 0; // global_counter is not const; it may change at runtime
constinit applies to variables with static or thread storage duration and requires static initialization. It does not make the variable immutable, and it is not a general replacement for constexpr.
When to use each
| Keyword | Main meaning |
|---|---|
| const | Not modifiable through this name |
| constexpr | Can be used in constant expressions when possible |
| consteval | Calls must be evaluated at compile time |
| constinit | Static/thread variable must be statically initialized |
| static_assert | Compile-time condition check |
Common mistakes
- Assuming every constexpr function call is compile-time.
- Assuming constinit makes an object const.
- Passing runtime data to a consteval function.
- Using static_assert for a condition that is only known at runtime.
Practice
- Write a constexpr function that returns the larger of two integers.
- Use static_assert to check its result for known inputs.
- Explain why a constinit variable can still be modified.
Show solutions
- Define
constexpr int larger(int a, int b) { return a > b ? a : b; }. It returns the greater argument and can be evaluated at compile time for constant inputs. - Place
static_assert(larger(4, 9) == 9);after that definition. Compilation succeeds because the condition is true; changing the expected result to 8 makes compilation fail. - constinit guarantees initialization timing, not immutability.
Summary
Use constexpr for values and functions that can support constant evaluation, consteval when compile-time evaluation is mandatory, constinit for static initialization guarantees, and static_assert for compile-time checks.