constexpr, consteval & Compile-Time Programming
Move computation from runtime to compile time using constexpr, consteval, and constinit. Build lookup tables, validate configurations, and use constexpr containers.
Compile-Time Programming in C++
C++ uniquely allows you to run real code at compile time. This means the compiler can compute values, validate inputs, build lookup tables, and even run complex algorithms — all before your program ships. The result is embedded directly into the binary as constants.
The evolution of compile-time programming in C++:
- C++11:constexprintroduced — simple, single-return-statement functions
- C++14: Relaxedconstexpr— loops, local variables, multiple statements
- C++17:if constexpr— compile-time branching that discards untaken paths
- C++20:consteval(must be compile-time),constinit(ensures static init is compile-time),constexprcontainers (vector,string)
- C++23:if consteval— detect whether you are in a constant evaluation context
constexpr Functions
A constexprfunction can be evaluated at compile time or runtime. If called with compile-time arguments in a context that requires a constant (template argument,constexprvariable,static_assert, array size), the compiler evaluates it at compile time. Otherwise, it runs at runtime like a normal function.
#include <iostream>
#include <array>
#include <cstdint>
constexpr int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
constexpr int fibonacci(int n) {
if (n <= 1) return n;
int a = 0, b = 1;
for (int i = 2; i <= n; ++i) {
int next = a + b;
a = b;
b = next;
}
return b;
}
int main() {
// Compile-time evaluation: used in constexpr context
constexpr int fact10 = factorial(10); // computed at compile time
static_assert(fact10 == 3628800);
constexpr int fib10 = fibonacci(10); // computed at compile time
static_assert(fib10 == 55);
// Compile-time array sizes
std::array<int, factorial(5)> arr{}; // array of 120 elements
// Runtime evaluation: argument not constexpr
int n;
std::cin >> n;
std::cout << "factorial(" << n << ") = " << factorial(n) << '\n';
// Same function, now runs at runtime
}consteval & constinit (C++20)
consteval marks a function that must be evaluated at compile time — calling it with runtime values is a compile error. This is called an "immediate function." constinit ensures a variable with static or thread-local storage duration is initialized at compile time, preventing the "static initialization order fiasco" — but unlikeconstexpr, the variable can be modified after initialization.
#include <iostream>
// consteval: MUST be evaluated at compile time
consteval int square(int x) {
return x * x;
}
// constinit: must be initialized at compile time,
// but can be modified at runtime
constinit int global_value = square(7); // 49, computed at compile time
// constexpr variable: initialized at compile time AND immutable
constexpr int fixed_value = square(10); // 100, compile time, cannot change
int main() {
constexpr int a = square(5); // OK: compile-time context
static_assert(a == 25);
// int x = 5;
// int b = square(x); // ERROR: consteval requires compile-time args
// constinit variable can be modified after initialization
global_value = 100; // OK: runtime modification allowed
std::cout << global_value << '\n'; // 100
// fixed_value = 200; // ERROR: constexpr variable is const
}if constexpr in Practice
if constexpr (C++17) evaluates a condition at compile time and discards the untaken branch entirely. The discarded branch is not instantiated — it does not need to be valid code for the given template arguments. This is fundamentally different from a regularif, where both branches must be valid.
Key rules:
1. The condition must be a compile-time constant expression.
2. In a template, the discarded branch is not instantiated for the current template arguments.
3. Outside a template, both branches must still be syntactically valid (they just won't execute).
4.if constexprcan appear in regular functions, not just templates — but discarding only applies in template instantiation.
5. Eachif constexprbranch has its own scope — variables declared in one branch are not visible in the other.
constexpr Containers (C++20)
C++20 allowsstd::vectorandstd::stringto be used in constexpr contexts. You can build, manipulate, and query containers at compile time — a massive expansion of compile-time programming capabilities. The constraint: transient allocation — memory allocated during compile-time evaluation must be freed before the constexpr evaluation ends.
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
#include <array>
// Build and process a vector at compile time (C++20)
constexpr int sum_of_squares(int n) {
std::vector<int> v;
for (int i = 1; i <= n; ++i) {
v.push_back(i * i);
}
return std::accumulate(v.begin(), v.end(), 0);
// vector memory freed here — transient allocation
}
// Compile-time string processing
constexpr std::size_t count_vowels(std::string_view sv) {
std::size_t count = 0;
for (char c : sv) {
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' ||
c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U') {
++count;
}
}
return count;
}
// Generate a lookup table at compile time
template <std::size_t N>
constexpr auto generate_squares() {
std::array<int, N> result{};
for (std::size_t i = 0; i < N; ++i) {
result[i] = static_cast<int>(i * i);
}
return result;
}
static_assert(sum_of_squares(5) == 55); // 1+4+9+16+25
static_assert(count_vowels("Hello World") == 3);
constexpr auto squares = generate_squares<10>();
static_assert(squares[7] == 49);Practical Compile-Time Programming
Compile-time computation is not just a parlor trick. Real-world uses include: building lookup tables embedded in the binary, validating configurations at compile time, computing hashes for compile-time string matching, and generating optimized code paths.
#include <array>
#include <cstdint>
#include <stdexcept>
// Compile-time CRC32 table generation
constexpr auto generate_crc32_table() {
std::array<uint32_t, 256> table{};
for (uint32_t i = 0; i < 256; ++i) {
uint32_t crc = i;
for (int j = 0; j < 8; ++j) {
crc = (crc >> 1) ^ (0xEDB88320 * (crc & 1));
}
table[i] = crc;
}
return table;
}
constexpr auto crc32_table = generate_crc32_table();
// Compile-time configuration validation
struct Config {
int port;
int max_connections;
int timeout_ms;
};
consteval Config validated_config(int port, int max_conn, int timeout) {
if (port < 1 || port > 65535)
throw "Port out of range"; // compile error if invalid!
if (max_conn < 1 || max_conn > 10000)
throw "Invalid max connections";
if (timeout < 0)
throw "Negative timeout";
return {port, max_conn, timeout};
}
// This validates at compile time — typo or bad value = build fails
constexpr auto config = validated_config(8080, 1000, 5000); // OK
// constexpr auto bad = validated_config(99999, 1000, 5000); // ERROR!Common mistakes with compile-time programming:
1. Assumingconstexprmeans compile-time only — aconstexprfunction can run at runtime too. Onlyconstevalguarantees compile-time evaluation.
2. Transient allocation — in C++20 constexpr contexts, all memory allocated during evaluation must be freed before the evaluation completes. You cannot store aconstexpr std::vectoras a global (its heap memory would persist).
3. Not all functions are constexpr-safe — I/O, thread creation,reinterpret_cast, and inline assembly cannot appear in constexpr contexts.
4. Overusing constexpr — computing everything at compile time increases build times. Reserve it for values that genuinely benefit from being compile-time constants.
constexprfunctions run at compile time OR runtime depending on contextconsteval(C++20) vynutí vyhodnocení pouze v době kompilace – argumenty za běhu jsou chyby kompilaceconstinit(C++20) zajišťuje inicializaci statických/místních proměnných během kompilace, ale umožňuje modifikaci za běhuif constexprzahodí nepřevzatou větev, která nemusí být platná pro dané argumenty šablony- C++20 umožňuje
std::vectorastd::stringv kontextu constexpr s přechodnou alokací - Praktická použití zahrnují generování vyhledávací tabulky, ověřování konfigurace a hašování v době kompilace
Kvíz — Otestujte své znalosti
(15 XP)1. Jaký je rozdíl mezi`constexpr`a`consteval`?
2. Můžete mít`constexpr std::vector<int>`jako globální proměnnou v C++20?
3. Co`constinit`zaručuje?