Moving work and checks to compile time catches bugs earlier and removes runtime cost, and modern C++ lets you do it with normal code.
A constexpr function can be evaluated by the compiler when its arguments are constants, and is an ordinary function otherwise. Modern constexpr allows loops, local variables, if, switch, and since C++20 even virtual calls, try blocks, and std::vector/std::string used temporarily within the evaluation.
constexprvariables must be initialized with a constant expression and are implicitlyconst. They can size arrays, be template arguments and be used instatic_assert.static_assert(cond, "msg")fails the build ifcondis false, documenting assumptions about sizes, layouts and constants.- C++20
constevalmakes an immediate function: every call must be evaluated at compile time. Use it when a runtime call would be a mistake, like validating format strings. - C++20
constinitguarantees a global is initialized at compile time (avoiding the static initialization order problem) while staying mutable. if constexpr (cond)discards the untaken branch at compile time inside templates, so it doesn't even need to compile for that type.
A nice trick is building lookup tables with an immediately invoked lambda: constexpr auto table = [] { ... return t; }();. The table is computed by the compiler and stored in the binary.
A huge benefit: undefined behaviour isn't allowed in constant evaluation. Overflow, out-of-bounds access and use of uninitialized values become compile errors, so static_assert tests of constexpr functions double as UB checks.
std::is_constant_evaluated() (C++20) and C++23's if consteval let a function choose a different implementation at compile time, for example avoiding intrinsics the compiler can't evaluate.
Compile-time work isn't free: it costs build time, and compilers limit evaluation steps. Use it for tables, validation and configuration, not big computations.
Example
#include <array>
#include <iostream>
#include <string_view>
constexpr unsigned long long fib(int n) { // usable at compile time AND run time
unsigned long long a = 0, b = 1;
for (int i = 0; i < n; ++i) { auto t = a + b; a = b; b = t; }
return a;
}
consteval std::size_t count_vowels(std::string_view s) { // compile time only
std::size_t n = 0;
for (char c : s) n += std::string_view{"aeiou"}.find(c) != std::string_view::npos;
return n;
}
constexpr auto table = [] { // lookup table built by the compiler
std::array<unsigned long long, 20> t{};
for (int i = 0; i < 20; ++i) t[i] = fib(i);
return t;
}();
static_assert(fib(10) == 55); // checked during compilation
constinit int counter = 0; // compile-time init, still mutable
int main(int argc, char**) {
std::cout << table[19] << ' ' << count_vowels("constexpr") << '\n'; // 4181 2
std::cout << fib(argc + 5) << '\n'; // runtime call: 8 when run with no args
++counter;
}Remember
constexprfunctions run at compile time when given constants, at run time otherwiseconstevalforces compile time;constinitforces static initialisation- UB is a compile error in constant evaluation, so
static_asserttests catch it
Watch out for
- Assuming a
constexprfunction call is always computed at compile time — only contexts that require a constant guarantee it.