Skip to content

constexpr

Overview

constexpr is a keyword introduced in C++11 for declaring variables and functions that are evaluated at compile time. It shifts computation from runtime to compile time, achieving zero runtime overhead while preserving full type safety — a modern replacement for macros and template metaprogramming.

constexpr Variables

cpp
constexpr int max_size = 100;
constexpr double pi = 3.1415926535;
constexpr int arr_size = max_size + 1;  // depends on other constexpr

int arr[max_size];           // OK: array size
constexpr int* p = nullptr;  // OK: the pointer itself is constexpr

constexpr vs const

cpp
const int a = runtime_function();     // OK: runtime-initialized, read-only
constexpr int b = runtime_function(); // error: cannot evaluate at compile time

const is about "can it be modified"; constexpr is about "can it be computed at compile time."

Propertyconstconstexpr
MeaningRead-onlyCompile-time constant
Initialization timingCan be runtimeMust be compile time
Implies constYes (for variables)
Usable in arrays/templatesOnly when compile-time constantAlways

constexpr Functions

C++11 strictly restricts: the function body can only contain a single return statement (plus static_assert and type aliases).

cpp
constexpr int square(int x) { return x * x; }

constexpr int val = square(5);   // compile time: 25
int arr[square(5)];              // OK: array size

int runtime_val = 42;
int result = square(runtime_val); // runtime call is also allowed

C++11: Use Recursion Instead of Loops

cpp
// Error: C++11 does not allow local variables or loops
// constexpr int sum(int n) {
//     int s = 0; for (int i = 0; i <= n; ++i) s += i; return s;
// }

// Correct: recursion + ternary expression
constexpr int sum(int n) {
    return n <= 0 ? 0 : n + sum(n - 1);
}

Compile-Time Computation Examples

Fibonacci and Factorial

cpp
constexpr long long fib(int n) {
    return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}

constexpr unsigned long long factorial(int n) {
    return n <= 1 ? 1ULL : static_cast<unsigned long long>(n) * factorial(n - 1);
}

static_assert(fib(10) == 55, "");
static_assert(factorial(5) == 120, "");

Compile-Time Hash (FNV-1a)

cpp
constexpr uint32_t fnv1a_hash(const char* str, uint32_t basis = 2166136261u) {
    return *str == '\0'
        ? basis
        : fnv1a_hash(str + 1, (basis ^ static_cast<uint32_t>(*str)) * 16777619u);
}

// switch-case requires constant expression labels
void process(const char* tag) {
    switch (fnv1a_hash(tag)) {
        case fnv1a_hash("login"):  handle_login();  break;
        case fnv1a_hash("logout"): handle_logout(); break;
        default:                   handle_unknown(); break;
    }
}

constexpr vs #define Macros

cpp
#define SQUARE(x) ((x) * ((x)))   // textual substitution, side-effect risk
constexpr int square(int x) { return x * x; }  // type-safe, standard semantics
Property#defineconstexpr
Type safeNoYes
ScopeFile-globalFollows scope
Multiple evaluation of argumentsYesNo
DebuggableNoYes

constexpr Constructors and Member Functions

cpp
struct Point {
    int x, y;
    constexpr Point(int x, int y) : x(x), y(y) {}
    constexpr int manhattan() const {
        return (x >= 0 ? x : -x) + (y >= 0 ? y : -y);
    }
};

constexpr Point p(3, -4);
constexpr int d = p.manhattan();  // 7

Common Use Cases

Use CaseExample
Array sizeint buf[constexpr_size];
Template parametersstd::array<int, fib(10)>
case labelscase fnv1a_hash("tag"):
Replacing enum constantsconstexpr double timeout = 30.0; (enum can only hold integers)
Replacing template metaprogrammingconstexpr int v = f(n); is more intuitive than F<N>::value

C++14 Relaxations

cpp
// C++14: allows local variables, loops, multiple statements
constexpr int sum(int n) {
    int result = 0;
    for (int i = 1; i <= n; ++i) result += i;
    return result;
}

C++17 further allows constexpr if, and C++20 allows constexpr dynamic allocation and virtual functions.

Best Practices

PracticeExplanation
Prefer constexpr over #defineType-safe, debuggable
constexpr over constWhen the value is truly determinable at compile time
Compile-time computation over template metaprogrammingconstexpr functions are more intuitive than recursive templates
Use constexpr constructorsMakes custom types usable in compile-time contexts

Common Pitfalls

constexpr does not mean "must" evaluate at compile time — runtime calls are also legal:

cpp
constexpr int f(int x) { return x * 2; }
int n; std::cin >> n;
int r = f(n);  // OK: runtime evaluation

C++11 recursion depth limits — compilers typically limit 256–512 levels of recursion; exceeding this causes compilation failure (not stack overflow).

C++11 does not allow side effectsstd::cout, assignments, etc. are illegal in constexpr functions.

Comparison with Pre-C++11

FeatureC++03C++11 constexpr
Compile-time constantsenum / #defineconstexpr variables
Compile-time computationTemplate metaprogrammingconstexpr functions
Floating-point constants#defineconstexpr double
Type safetyMacros have no typeFull type system
ReadabilityExtremely poorConsistent with normal functions

constexpr is a key step from the dark ages of macros and template metaprogramming toward type-safe compile-time computation.

Released under the MIT License