C++ has always supported compile-time constants through macros and const
. However, with the evolution of the language from C++11 to C++20, new keywords have been introduced to enable better compile-time computation, safety, and performance:
constexpr
(C++11, enhanced in C++14 and C++20)consteval
(C++20)constinit
(C++20)
We will explore the differences, use-cases, and examples of these three powerful keywords.
constexpr
– Compile-Time Evaluated Expressions
What is constexpr
?
constexpr
indicates that a variable or function can be evaluated at compile time if its arguments are also constexpr.
- Introduced in C++11 for constants and simple functions
- C++14 allowed more complex bodies, control structures (
if
,for
) - C++20 added support for dynamic memory (
new
,delete
) insideconstexpr
When to Use
Use constexpr
when:
- You want a value or function to be usable at compile-time
- But you still allow fallback to run-time if needed
constexpr
Function Example
constexpr int square(int x) {
return x * x;
}
int main() {
constexpr int x = square(5); // evaluated at compile-time
int y = square(10); // evaluated at run-time
}
constexpr
Variable Example
constexpr double pi = 3.14159;
constexpr double area(double r) {
return pi * r * r;
}
User-defined types can use constexpr
:
struct Point {
int x, y;
constexpr Point(int x, int y) : x(x), y(y) {}
};
constexpr Point p1(10, 20); // OK: evaluated at compile-time
consteval
– Immediate Compile-Time Evaluation
What is consteval
?
consteval
functions must be evaluated at compile-time. They cannot be called at run-time.
- The function is meant only for compile-time constant generation
- Any attempt to call it at runtime should be a compilation error
consteval
Example
consteval int factorial(int n) {
return (n <= 1) ? 1 : (n * factorial(n - 1));
}
int main() {
constexpr int f = factorial(5); //OK
int x = 5;
// int f2 = factorial(x); //Error: x not constexpr
}
constinit
– Guaranteed Static Initialization
What is constinit
?
constinit
ensures a variable is statically initialized at compile time, not dynamically at runtime.
- Introduced in C++20
- Useful to avoid the Static Initialization Order Fiasco
When to Use
Use constinit
when:
- You want compile-time initialization
- But the variable is not constexpr
- The value might change later (so
constexpr
cannot be used)
constinit
Example
constinit int global_value = 100; // initialized at compile-time
void update() {
global_value = 200; //allowed: not constexpr
}
Real-world Example: Static Initialization Order Fiasco
#include <iostream>
constinit int globalA = 42;
int getA() {
return globalA;
}
int main() {
std::cout << getA() << "\n"; // Safe, globalA is constinit
}
Without constinit
, globalA
might get dynamically initialized after getA()
is called — leading to undefined behavior.
Conclusion
- Use
constexpr
for general-purpose compile-time constructs. - Use
consteval
when you want enforced compile-time logic. - Use
constinit
to avoid static initialization order issues without forcing immutability.