constexpr & compile-time computation
Scenario
Your game needs a massive lookup table of sine waves. You write a loop to calculate it, but it adds 2 seconds to the game's startup time on the user's machine.
How can you force your own compiler to do the math so the user's CPU doesn't have to?
Mental model
`constexpr` is like a chef prepping ingredients before the restaurant opens. Instead of chopping onions while the customer waits (runtime), the chef does it beforehand (compile-time) so the dish is instantly ready.
`constexpr` (constant expression) is a keyword that specifies the value of a variable or function *can* be evaluated strictly at compile-time. If all inputs are known at compile-time, the compiler *can* execute the C++ code itself. However, it is only *forced* to do so if the result is used in a context that requires a compile-time constant (like assigning to a `constexpr` variable). Otherwise, it might defer execution to runtime.
Explanation
**constexpr Variables:**
A `constexpr` variable must be initialized with a compile-time constant. It is inherently `const`, but strictly stronger.
`const int x = get_random();` is valid (runtime constant).
`constexpr int y = get_random();` is a compile error (cannot evaluate random at compile-time).
**constexpr Functions:**
A `constexpr` function *can* be evaluated at compile-time. If you pass it compile-time constants and assign the result to a `constexpr` variable, the compiler runs it and bakes the answer into the binary. (Note: C++20 introduced `consteval` which actually forces compile-time execution regardless of context). If you pass it runtime variables (like user input), it behaves like a normal, fast runtime function.
**The C++ Compile-Time Revolution:**
Since C++14, `constexpr` functions can contain loops, `if` statements, and local variables. Since C++20, you can even use `std::vector` and `std::string` inside `constexpr` functions (they are cleaned up before compilation finishes).
**Real-world Engineering:**
Embedded systems developers use `constexpr` obsessively. It allows them to write clean, readable math functions to generate lookup tables, bitmasks, or hardware register configurations, and guarantee that the embedded chip spends zero CPU cycles calculating them at runtime. It replaces C-style `#define` macros because unlike blind text-replacement macros, `constexpr` provides strict type safety, respects scoping rules (namespaces/classes), and can be debugged.
**The 'No Side-Effects' Rule:**
Because the compiler executes `constexpr` functions in isolation, they are strictly forbidden from having side-effects. You cannot perform File I/O, make network requests, or depend on external runtime state (like the OS).
Code examples
Compile-time calculation
#include <iostream>
// A constexpr function can be evaluated by the compiler
constexpr int fibonacci(int n) {
if (n <= 1) return n;
int a = 0, b = 1, c = 0;
for (int i = 2; i <= n; ++i) {
c = a + b;
a = b;
b = c;
}
return c;
}
int main() {
// Because 10 is known at compile time, the compiler runs the loop.
// The binary just contains the equivalent of: int result = 55;
constexpr int result = fibonacci(10);
std::cout << result << '\n';
int runtime_val;
std::cin >> runtime_val;
// Degrades gracefully to a normal runtime function
int runtime_result = fibonacci(runtime_val);
return 0;
}
By checking the assembly output of this program, you will literally see the number `55` hardcoded into the CPU instructions. The `for` loop does not exist in the final binary for the `constexpr` call.
Key points
- Shift work to the compiler: `constexpr` forces the compiler to do the heavy lifting, saving CPU cycles for the end-user.
- Graceful degradation: `constexpr` functions automatically act as normal runtime functions if given runtime data.
Common mistakes
- Confusing const and constexpr: `const` means "I promise not to change this value once it is initialized (which might happen at runtime)". `constexpr` means "This value is known exactly at compile-time." All `constexpr` variables are `const`, but not all `const` variables are `constexpr`.
Recall questions
- What happens when you pass runtime variables (like user input) into a `constexpr` function?
- What is the difference between a `const` variable and a `constexpr` variable?
- Why is `constexpr` preferred over preprocessor `#define` macros for constants?
Questions & answers
A developer marks a function `constexpr` and tries to open a file inside it using `std::ifstream`. The compiler throws an error. Why?
File I/O is fundamentally a runtime operation depending on the operating system and disk state. `constexpr` functions cannot contain operations that have side effects or depend on runtime state outside the compiler.
Approach: Identify the limitations of compile-time execution (no I/O, no network, no dynamic memory that leaks to runtime).
If you mark a function as `constexpr`, are you guaranteed that it will always execute at compile-time?
No. The `constexpr` keyword only means the function *can* be evaluated at compile time. Even with constant inputs, unless the result is assigned to a `constexpr` variable or used in a constant context, the compiler may execute it at runtime (e.g. in debug builds).
Approach: Understand the dual-nature of constexpr functions (compile-time vs runtime fallback).
Continue learning
Previous: Templates — function & class templates