Search Knowledge

© 2026 LIBREUNI PROJECT

Modern C++ Programming / Modern C++20/23

constexpr and consteval (C++20/23)

Compile-Time Computation Evolution

Modern C++ continuously expands compile-time capabilities. C++20 and C++23 bring significant improvements to constexpr and introduce consteval and constinit.

constexpr Evolution

C++11: Basic constexpr Functions

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

constexpr int result = square(5);  // Computed at compile-time
int runtime_val = square(n);       // Can still be used at runtime

C++14: Relaxed constexpr

Allows multiple statements, loops, and local variables:

constexpr int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n; ++i) {
        result *= i;
    }
    return result;
}

constexpr int fact5 = factorial(5);  // 120, computed at compile-time

C++17: constexpr Lambda

auto squared = [](int x) constexpr { return x * x; };
constexpr int val = squared(10);  // 100

C++20: Major Expansions

Virtual functions, try-catch, dynamic allocation, and more:

#include <vector>
#include <algorithm>

constexpr int sum_vector() {
    std::vector<int> vec{1, 2, 3, 4, 5};  // Dynamic allocation!
    int sum = 0;
    for (int x : vec) {
        sum += x;
    }
    return sum;
}

constexpr int result = sum_vector();  // 15, at compile-time

consteval: Immediate Functions (C++20)

Functions that must execute at compile-time:

consteval int compile_time_only(int x) {
    return x * x;
}

constexpr int value1 = compile_time_only(5);  // OK: compile-time
int n = 5;
int value2 = compile_time_only(n);            // ERROR: not compile-time

Use Cases for consteval

Generate compile-time constants:

consteval unsigned hash_string(const char* str) {
    unsigned hash = 0;
    while (*str) {
        hash = hash * 31 + *str++;
    }
    return hash;
}

switch (event_type) {
    case hash_string("click"):    // Computed at compile-time
        handle_click();
        break;
    case hash_string("keypress"):
        handle_key();
        break;
}

consteval vs constexpr

constexpr int maybe_compile_time(int x) {
    return x * 2;
}

consteval int always_compile_time(int x) {
    return x * 2;
}

int runtime_value = 10;

int a = maybe_compile_time(5);              // OK: compile-time
int b = maybe_compile_time(runtime_value);  // OK: runtime

int c = always_compile_time(5);              // OK: compile-time
int d = always_compile_time(runtime_value);  // ERROR!

constinit: Compile-Time Initialization (C++20)

Ensures variables are initialized at compile-time, but allows runtime modification:

constinit int global_counter = 42;  // Must be compile-time initialized

void increment() {
    ++global_counter;  // OK: can modify at runtime
}

// constexpr would make it immutable
constexpr int immutable = 42;
// immutable = 43;  // ERROR: cannot modify

// constinit ensures initialization, allows modification
constinit int mutable_init = compute_value();  // compute_value() must be constexpr

Static Storage Duration

constinit is primarily for static/thread-local variables:

constinit static int initialized_once = expensive_computation();
// Guarantees no dynamic initialization order issues

Prevents Static Initialization Order Fiasco

// header.h
constinit extern int global_config;  // Declaration

// source.cpp
constinit int global_config = compute_config();  // Must be compile-time

// other.cpp
// Safe to use global_config - guaranteed initialized
int value = global_config + 10;

if consteval (C++23)

Different code paths for compile-time vs runtime:

constexpr int compute(int x) {
    if consteval {
        // This branch runs only at compile-time
        return expensive_compile_time_computation(x);
    } else {
        // This branch runs only at runtime
        return fast_runtime_computation(x);
    }
}

Practical Example

consteval int compile_time_sqrt(int x) {
    // Simple algorithm acceptable for compile-time
    int result = 0;
    while (result * result < x) ++result;
    return result;
}

int runtime_sqrt(int x) {
    // Optimized runtime algorithm
    return std::sqrt(x);
}

constexpr int safe_sqrt(int x) {
    if consteval {
        return compile_time_sqrt(x);
    } else {
        return runtime_sqrt(x);
    }
}

constexpr std::string and std::vector (C++20)

constexpr std::string build_greeting() {
    std::string greeting = "Hello, ";
    greeting += "World!";
    return greeting;
}

constexpr auto msg = build_greeting();  // Compile-time!
constexpr auto compute_primes(int max) {
    std::vector<int> primes;
    for (int n = 2; n <= max; ++n) {
        bool is_prime = true;
        for (int p : primes) {
            if (n % p == 0) {
                is_prime = false;
                break;
            }
        }
        if (is_prime) primes.push_back(n);
    }
    return primes;
}

constexpr auto primes = compute_primes(20);
// std::array or similar at compile-time

constexpr Virtual Functions (C++20)

struct Shape {
    virtual constexpr double area() const = 0;
    virtual ~Shape() = default;
};

struct Circle : Shape {
    double radius;
    
    constexpr Circle(double r) : radius(r) {}
    
    constexpr double area() const override {
        return 3.14159 * radius * radius;
    }
};

constexpr double compute_area() {
    Circle c{5.0};
    Shape& s = c;
    return s.area();  // Virtual call at compile-time!
}

constexpr double area = compute_area();  // 78.5398...

try-catch in constexpr (C++20)

constexpr int safe_divide(int a, int b) {
    try {
        if (b == 0) throw std::runtime_error("Division by zero");
        return a / b;
    } catch (...) {
        return 0;  // Error value
    }
}

constexpr int result = safe_divide(10, 2);  // 5

Note: Throwing exceptions causes compile-time evaluation to fail, but the structure is allowed.

std::is_constant_evaluated()

Detect compile-time evaluation:

#include <type_traits>

constexpr int compute(int x) {
    if (std::is_constant_evaluated()) {
        // Compile-time: use simple algorithm
        return x * x;
    } else {
        // Runtime: can use optimized version
        return fast_multiply(x, x);
    }
}

Practical Examples

Compile-Time Configuration

consteval auto get_build_config() {
    struct Config {
        const char* version;
        int optimization_level;
        bool debug;
    };
    
    return Config{
        .version = "1.0.0",
        .optimization_level = 2,
        .debug = false
    };
}

constexpr auto config = get_build_config();

Compile-Time Validation

consteval void validate_range(int min, int max) {
    if (min >= max) {
        throw "Invalid range";  // Compile-time error
    }
}

template<int Min, int Max>
struct Range {
    Range() {
        validate_range(Min, Max);  // Compile-time check
    }
};

Range<1, 10> valid;      // OK
Range<10, 1> invalid;    // Compile error!
Conceptual Check

What is the key difference between constexpr and consteval functions?

Interactive Lab

Complete the Code

// Create a function that MUST execute at compile-time
 int cube(int x) {
    return x * x * x;
}

constexpr int result = cube(3);  // Must work at compile-time
Runtime Environment

Interactive Lab

1#include <iostream>
2 
3constexpr int factorial(int n) {
4 int result = 1;
5 for (int i = 2; i <= n; ++i) {
6 result *= i;
7 }
8 return result;
9}
10 
11consteval int square(int x) {
12 return x * x;
13}
14 
15int main() {
16 constexpr int fact5 = factorial(5);
17 constexpr int sq10 = square(10);
18
19 std::cout << "5! = " << fact5 << '\n';
20 std::cout << "10² = " << sq10 << '\n';
21
22 // This works:
23 int n = 5;
24 std::cout << "factorial(" << n << ") = " << factorial(n) << '\n';
25
26 // This would fail: square(n) - consteval requires compile-time
27
28 return 0;
29}
System Console

Waiting for signal...