constexprSpecifier Reference (cppreference)- ISO/IEC 14882 C++ Standard Sections:
[dcl.constexpr],[expr.const]
The constexpr specifier declares that the value of a variable, function, or constructor can be evaluated at compile time within constant expressions. Introduced in C++11 and significantly expanded across subsequent standards, constexpr elevates compile-time computation from archaic template metaprogramming hacks into idiomatic C++ code. It guarantees zero-runtime-cost calculations, enables static validation via static_assert, eliminates static initialization order bugs, and bridges the boundary between compile-time meta-execution and runtime processing.
Prior to C++11, executing computations at compile time required severe language workarounds:
- C-Style Preprocessor Macros (
#define):- Macros lacked type safety, respect for scope or namespaces, and could not perform complex algorithmic logic without severe macro expansion bugs.
- Template Metaprogramming (TMP) Hacks:
- Performing basic arithmetic at compile time required recursive struct specializations and enum hacks:
// C++98 Template Metaprogramming Fibonacci Hack template <int N> struct Fib { enum { value = Fib<N - 1>::value + Fib<N - 2>::value }; }; template <> struct Fib<0> { enum { value = 0 }; }; template <> struct Fib<1> { enum { value = 1 }; };
- TMP generated opaque compiler diagnostics, inflated translation unit memory footprints, and degraded compilation speeds exponentially.
- Performing basic arithmetic at compile time required recursive struct specializations and enum hacks:
- Static Initialization Order Fiasco:
- Global or static constants computed at program startup resulted in undefined behavior if dependent static objects in different translation units were initialized out of order.
Solution: C++11 introduced constexpr, allowing standard C++ functions to execute directly within the compiler's abstract machine interpreter at compile time while maintaining full type safety and normal C++ syntax.
A constexpr function possesses dual execution capabilities:
- Compile-Time Evaluation: Triggered when the function is invoked in a context requiring a constant expression (e.g.,
static_assert, array bounds, template non-type parameters, or initializing aconstexprvariable) provided all arguments are constant expressions. - Runtime Evaluation: If called with runtime variables or in non-constant contexts, the compiler generates standard machine instructions and executes the function at runtime without duplicate code definitions.
constexpr int square(int x) { return x * x; }
int main() {
constexpr int c_val = square(5); // Forced Compile-Time Execution (Evaluated to 25)
int r_arg = 5;
int r_val = square(r_arg); // Runtime Execution (Standard function call)
}| Specifier | Standard Introduced | Primary Purpose | Runtime Execution Allowed? | Immutability |
|---|---|---|---|---|
constexpr |
C++11 | Specifies that a variable/function can be evaluated at compile time. | Yes (if arguments are runtime variables). | Variables are implicitly const. |
consteval |
C++20 | Immediate Functions: Must produce a constant expression at compile time. | No (Attempting runtime call is a compile error). | Functions only. |
constinit |
C++20 | Guarantees static/thread-local variable initialization occurs at compile time. | N/A (Applies to variable initialization). | Variable remains mutable at runtime. |
┌──────────────────────────────────────────────────────────────────┐
│ C++11 │
├──────────────────────────────────────────────────────────────────┤
│ • Introduced constexpr for variables and functions │
│ • Strict restrictions: Single return statement (ternary logic), │
│ no local variable declarations, no loops, no mutations │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ C++14 │
├──────────────────────────────────────────────────────────────────┤
│ • Relaxed rules: Allowed local variable declarations, loops │
│ (for/while), if/switch control flow, and local mutations │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ C++17 │
├──────────────────────────────────────────────────────────────────┤
│ • constexpr lambdas and constexpr if (if constexpr) │
│ • Standard library additions: std::array, std::string_view │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ C++20 │
├──────────────────────────────────────────────────────────────────┤
│ • Transient dynamic memory allocation (constexpr new / delete) │
│ • std::vector and std::string become constexpr-capable │
│ • Virtual functions, try-catch blocks, and std::is_constant_eval │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ C++23 / C++26 │
├──────────────────────────────────────────────────────────────────┤
│ • Non-constexpr variables allowed if unreached in evaluation │
│ • constexpr cast from void* (C++26) │
│ • constexpr std::construct_at placement new (C++26) │
│ • Expanded math and system error constexpr library coverage │
└──────────────────────────────────────────────────────────────────┘
Demonstrates the shift from C++11 ternary single-return restrictions to modern, imperative loops.
#include <iostream>
#include <array>
// C++11 Style: Constrained to a single return statement using recursion/ternary
constexpr std::size_t gcd_cpp11(std::size_t a, std::size_t b) {
return (b == 0) ? a : gcd_cpp11(b, a % b);
}
// C++14 Style: Full imperative control flow (loops, local mutation)
constexpr std::size_t gcd_cpp14(std::size_t a, std::size_t b) {
while (b != 0) {
std::size_t temp = b;
b = a % b;
a = temp;
}
return a;
}
int main() {
// Verified entirely at compile-time via static_assert
static_assert(gcd_cpp11(48, 18) == 6, "C++11 GCD Compile-Time Calculation Failed");
static_assert(gcd_cpp14(48, 18) == 6, "C++14 GCD Compile-Time Calculation Failed");
std::array<int, gcd_cpp14(12, 8)> compile_time_array{}; // Array size: 4
std::cout << "Array size generated from constexpr GCD: " << compile_time_array.size() << "\n";
return 0;
}Demonstrates compile-time vector manipulations using transient dynamic memory allocation (new/delete) and contextual evaluation branches.
#include <iostream>
#include <vector>
#include <numeric>
#include <type_traits>
// Compile-time vector processing (C++20 transient allocation)
constexpr int compute_sum_at_compile_time(int n) {
std::vector<int> data; // Dynamic allocation at compile-time!
for (int i = 1; i <= n; ++i) {
data.push_back(i);
}
return std::accumulate(data.begin(), data.end(), 0);
}
// C++23 'if consteval' conditional execution
constexpr double optimized_power(double base, int exp) {
if consteval {
// High-precision iterative algorithm during compile-time evaluation
double res = 1.0;
for (int i = 0; i < exp; ++i) res *= base;
return res;
} else {
// Delegate to hardware/SIMD intrinsic operations during runtime
std::cout << "[Runtime Execution Path Invoked]\n";
return std::pow(base, exp);
}
}
int main() {
// Transient allocation must be completely freed before constant evaluation ends
constexpr int sum_100 = compute_sum_at_compile_time(100);
static_assert(sum_100 == 5050, "Compile-time vector calculation failed!");
// Compile-time branch
constexpr double static_pow = optimized_power(2.0, 10);
static_assert(static_pow == 1024.0);
// Runtime branch
double b = 2.0;
double run_pow = optimized_power(b, 10);
std::cout << "Runtime power result: " << run_pow << "\n";
return 0;
}Demonstrates casting typed pointers through void* inside constexpr evaluation contexts (C++26).
#include <iostream>
#include <memory>
struct GeometryNode {
int id;
double scale;
};
// C++26 allows casting typed pointers to void* and back within constant expressions
constexpr int parse_opaque_node(GeometryNode* node) {
void* opaque_ptr = node; // Cast to void*
// C++26: Casting void* back to original pointer type in constexpr is valid!
GeometryNode* typed_ptr = static_cast<GeometryNode*>(opaque_ptr);
return typed_ptr->id;
}
int main() {
constexpr GeometryNode node{101, 2.5};
constexpr int node_id = parse_opaque_node(const_cast<GeometryNode*>(&node));
static_assert(node_id == 101, "C++26 constexpr void* cast failed");
std::cout << "Successfully parsed node ID at compile-time: " << node_id << "\n";
return 0;
}-
Undefined Behavior (UB) Aborts Compilation:
- Any execution path containing Undefined Behavior (e.g., array out-of-bounds, uninitialized variable read, integer overflow, pointer aliasing violations) will cause the compiler to halt compilation immediately with a detailed evaluation trace.
-
constexprDoes Not Guarantee Compile-Time Execution for Non-Constant Contexts:- Writing
int val = fib(10);may execute at runtime even iffibisconstexpr. - Fix: Enforce compile-time execution using
constexpr int val = fib(10);orconstevalfunctions (C++20).
- Writing
-
Transient Memory Allocation Lifetime Rule (C++20):
- Dynamic memory allocated via
newinside aconstexprfunction must be deallocated viadeletebefore the constant evaluation phase finishes. Memory allocated at compile time cannot leak into runtime execution memory.
- Dynamic memory allocated via
- Related to:
- constexpr If — Compile-time conditional branch elimination (
if constexpr). - constexpr Lambda — Compile-time execution of lambdas.
- constexpr Cast From Void* — C++26 void pointer casting in constant evaluation.
consteval&constinit(C++20) — Immediate functions and guaranteed static initialization.
- constexpr If — Compile-time conditional branch elimination (
- Superseded by: None.
- Supersedes: Preprocessor macro constants (
#define) and recursive template metaprogramming hacks.
Back to C++11 section.