C++ Compile-Time Programming: constexpr, consteval, constinit & Template Metaprogramming (C++23)

C++ Compile-Time Programming: constexpr, consteval, constinit & Template Metaprogramming (C++23)
Table of Contents
- The Four Compile-Time Keywords
- constexpr Functions: Rules and Restrictions
- constexpr Classes and Objects (C++14/20)
- consteval: Mandatory Compile-Time Functions (C++20)
- constinit: Initialization Order Guarantee (C++20)
- std::is_constant_evaluated: Dual-Mode Functions (C++20)
- Compile-Time Lookup Tables with IIFE
- constexpr Containers: vector, string in C++20
- Type Traits and std::integral_constant
- static_assert: Compile-Time Validation
- Template Metaprogramming: Computing at Compile Time
- Frequently Asked Questions
- Key Takeaway
The Four Compile-Time Keywords
constexpr Functions: Rules and Restrictions
A constexpr function can execute at compile time if called with constant expressions. If called with runtime values, it runs normally as a runtime function:
Rules for constexpr functions:
- No goto, thread-local storage, or I/O (
printf,fopen, etc.) - No undefined behavior — the compiler catches it
- Can call only
constexprfunctions - Local variables of non-literal types require C++14+
constexpr Classes and Objects (C++14/20)
consteval: Mandatory Compile-Time Functions (C++20)
consteval ("immediate function") forces execution at compile time. If the compiler cannot evaluate the call at compile time (because arguments aren't constant expressions), it's a hard compile error:
constinit: Initialization Order Guarantee (C++20)
The "Static Initialization Order Fiasco" occurs when one global's initialization depends on another global that may not be initialized yet:
Compile-Time Lookup Tables with IIFE
Generate lookup tables entirely at compile time — binary contains pre-computed data:
Type Traits and std::integral_constant
Type traits let you query properties of types at compile time:
static_assert: Compile-Time Validation
Frequently Asked Questions
Does constexpr always run at compile time?
No — constexpr means "allowed at compile time if called with constant expressions." If called with runtime values, it executes at runtime normally. Use consteval when you want to guarantee compile-time execution and make runtime calls a hard error. Use if (std::is_constant_evaluated()) to write functions that behave differently in each context.
Why would constexpr slow down my build?
Compile-time computation uses the compiler as an interpreter. Complex constexpr computations — sorting large arrays, generating CRC tables — move computation to build time. This increases build time but produces a faster binary. For very complex constexpr (400+ line recursive templates), builds can become noticeably slower. Profile build time with -ftime-report (GCC) or clang -ftime-trace.
What is the "static initialization order fiasco" and how does constinit help?
In C++, the initialization order of global variables across translation units (.cpp files) is undefined. If global_b (in file2.cpp) depends on global_a (in file1.cpp), global_a might not be initialized yet when global_b's initializer runs. constinit prevents this by requiring the initializer to be a constant expression (evaluated at compile time) — no runtime initialization = no initialization order dependency.
Key Takeaway
Compile-time programming in C++ is not just a performance trick — it's a correctness mechanism. Compile-time validated format strings catch bugs before the program ships. Compile-time lookup tables eliminate an entire category of runtime computation. static_assert enforces struct layout contracts across platforms. consteval makes misuse a compile error rather than a runtime crash. Every time you push work left — from runtime to compile time — you get a faster binary and a safer system.
Read next: Type Traits & static_assert: Defensive Programming →
Part of the C++ Mastery Course — 30 modules from modern C++ basics to expert systems engineering.
