[2026] C++ constexpr Functions and Variables: Compute at Compile Time [#26-1]

[2026] C++ constexpr Functions and Variables: Compute at Compile Time [#26-1]

이 글의 핵심

Master C++ constexpr: compile-time constants, constexpr functions, literal types, C++14/20 rules, if constexpr, consteval, lookup tables, and common errors—practical patterns for array sizes and templates.

Introduction: “I want this computed at compile time”

Problem scenarios

When building C++ projects you often hit:

  • You need an array size from a runtime variable and int arr[n] fails to compile.
  • CRC32, hash tables, or similar values are recomputed every run even though they are fixed.
  • You want configuration (buffer sizes, timeouts) computed once at build time, not hard-coded magic numbers.
  • You need template arguments like std::array<int, 128> but cannot use getBufferSize() unless it is a constant expression.

More scenarios

Scenario 1: Fixed protocol buffer size
Packet header is always 16 bytes and max payload 4096; you want std::array<uint8_t, 16 + 4096> with the size known at compile time—constexpr keeps this maintainable without macros. Scenario 2: Base64 lookup table
The 64-character table never changes at runtime; building it with constexpr removes initialization cost and places data in .rodata. Scenario 3: JSON schema limits
Max fields and string lengths defined in a schema can be turned into compile-time constants for std::array sizes and static_assert. Scenario 4: Embedded memory map
Register offsets and region sizes per board can be constexpr switch cases or template parameters. Scenario 5: Deterministic unit tests
Fixed seeds for reproducible random sequences with zero runtime seed computation. In C++, array sizes, template non-type parameters, switch cases, etc., require constant expressions—values fixed at compile time. Runtime values cannot be used in those positions. constexpr marks functions and variables that can be evaluated at compile time, so you can use their results wherever a constant expression is required. From C++14 onward, loops and multiple statements are allowed; C++20 extends constexpr further across the standard library.

Goals

  • constexpr variables and constexpr functions
  • Relaxed rules in C++14/20
  • Overview of if constexpr
  • Typical errors and production patterns

When constexpr is evaluated

아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

flowchart TD
    A[constexpr function call] --> B{Arguments constant expressions?}
    B -->|Yes| C[Evaluated at compile time]
    B -->|No| D[Evaluated at runtime]
    C --> E[Usable for array sizes, template args, etc.]
    D --> F[Behaves like a normal function]

constexpr vs macros

아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

flowchart LR
    subgraph macro[Macros]
        M1[Preprocessor substitution] --> M2[No type checking]
        M2 --> M3[Hard to debug]
    end
    subgraph constexpr[constexpr]
        C1[Compiler evaluation] --> C2[Type checking]
        C2 --> C3[Namespaces and scope]
        C3 --> C4[Overloading and templates]
    end

After reading this post

  • Define compile-time constants with constexpr.
  • Understand constexpr function constraints and extensions.
  • Use results for array sizes and template arguments.
  • Distinguish const vs constexpr.
  • Apply patterns (lookup tables, config parsing).

Real-world pain

Lessons from applying these ideas in larger projects.

Situation and fix

Theory from books often diverges from real code. Code review and profiling surfaced inefficiencies; hands-on iteration mattered.

Table of contents

  1. constexpr variables
  2. constexpr functions in detail
  3. constexpr variables and constructors
  4. constexpr vs const
  5. C++14/20 extensions
  6. Common errors
  7. Performance benchmarks
  8. Production patterns
  9. Practical use

1. constexpr variables

Compile-time constants

A constexpr variable must have its value fixed at compile time. You can use it for raw array sizes and std::array template arguments. A const variable initialized at runtime is read-only but not a constant expression for array bounds. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

constexpr int MAX = 100;
constexpr double PI = 3.14159265358979;
int arr[MAX];  // OK: constant expression
std::array<int, MAX> a;  // OK

Initialization rules

constexpr variables must be initialized from literals or other constexpr values—not from arbitrary runtime calls. 아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

constexpr int a = 42;           // OK: literal
constexpr int b = a + 1;        // OK: another constexpr
constexpr int c = add(2, 3);    // OK if add is constexpr and args are constant
int runtime_val = getValue();
constexpr int d = runtime_val;  // Compile error!

2. constexpr functions

Basics

A constexpr function can be invoked at compile time when its arguments are constant expressions. add(3, 5) folds to 8 at compile time; you can still call add(i, j) at runtime with variable arguments. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <iostream>
constexpr int add(int a, int b) {
    return a + b;
}
int main() {
    constexpr int x = add(3, 5);  // 8 at compile time
    std::cout << x << "\n";
    return 0;
}

Output: a single line printing 8.

Example 1: factorial (C++14)

C++14 allows if, multiple return statements, and recursion in constexpr functions. 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

constexpr unsigned long long factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}
int main() {
    constexpr auto f5 = factorial(5);  // 120 at compile time
    std::array<int, factorial(5)> arr; // size 120
    return 0;
}

Example 2: Fibonacci

constexpr functions are often easier to read than recursive templates. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

constexpr int fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
}
template <int N>
struct Fib {
    static constexpr int value = fib(N);
};
int main() {
    constexpr int f10 = fib(10);  // 55
    constexpr int f20 = Fib<20>::value;  // 6765
    return 0;
}

Example 3: next power of two (loop)

아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

constexpr size_t nextPowerOfTwo(size_t n) {
    size_t p = 1;
    while (p < n) p *= 2;
    return p;
}
std::array<int, nextPowerOfTwo(100)> buf;  // size 128

Example 4: string length (C++14)

아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

constexpr size_t strLen(const char* s) {
    size_t len = 0;
    while (s[len] != '\0') ++len;
    return len;
}
constexpr size_t LEN = strLen("hello");  // 5
std::array<char, strLen("hello") + 1> buf;

Example 5: conditional clamp

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

constexpr int clamp(int value, int min_val, int max_val) {
    if (value < min_val) return min_val;
    if (value > max_val) return max_val;
    return value;
}
constexpr int c = clamp(150, 0, 100);  // 100

Example 6: CRC32 lookup table (full example)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <array>
#include <cstdint>
#include <utility>
constexpr uint32_t crc32_table_entry(uint32_t idx) {
    uint32_t crc = idx;
    for (int i = 0; i < 8; ++i) {
        crc = (crc >> 1) ^ (0xEDB88320u & -(crc & 1));
    }
    return crc;
}
template <size_t....Is>
constexpr auto make_crc32_table(std::index_sequence<Is...>) {
    return std::array<uint32_t, sizeof...(Is)>{{crc32_table_entry(static_cast<uint32_t>(Is))...}};
}
constexpr auto CRC32_TABLE = make_crc32_table(std::make_index_sequence<256>{});
uint32_t crc32(const uint8_t* data, size_t len) {
    uint32_t crc = 0xFFFFFFFFu;
    for (size_t i = 0; i < len; ++i) {
        crc = CRC32_TABLE[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
    }
    return crc ^ 0xFFFFFFFFu;
}

Benefit: CRC32_TABLE is built once at compile time and lives in .rodata.

Example 7: compile-time string hash for switch

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

constexpr unsigned long long hash_fnv1a(const char* str) {
    unsigned long long hash = 14695981039346656037ULL;
    while (*str) {
        hash ^= static_cast<unsigned long long>(*str++);
        hash *= 1099511628211ULL;
    }
    return hash;
}
constexpr auto CMD_HASH = hash_fnv1a("start");
void handle_command(const char* cmd) {
    switch (hash_fnv1a(cmd)) {
        case CMD_HASH:
            break;
        default:
            break;
    }
}

3. constexpr variables and constructors

Literal types

constexpr variables require literal types: scalars, arrays, and user-defined types with constexpr constructors.

constexpr constructor example

아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

struct Point {
    int x, y;
    constexpr Point(int x, int y) : x(x), y(y) {}
    constexpr int sum() const { return x + y; }
};
constexpr Point p(1, 2);
constexpr int s = p.sum();  // 3

Constraints

  • Constructor body must only call other constexpr-friendly operations.
  • All members must be constexpr-initializable.
  • No virtual inheritance or virtual calls (pre-C++20). 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct Vec3 {
    float x, y, z;
    constexpr Vec3(float x, float y, float z) : x(x), y(y), z(z) {}
    constexpr float lengthSq() const {
        return x * x + y * y + z * z;
    }
};
constexpr Vec3 v(1.0f, 0.0f, 0.0f);
constexpr float lenSq = v.lengthSq();  // 1.0

4. constexpr vs const

Aspectconstconstexpr
MeaningNot modified after initComputable at compile time
InitMay be runtimeMust be compile time
Constant expressionNo (if runtime init)Yes
Array boundsOnly if compile-time initYes
다음은 cpp를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 변수 선언 및 초기화
int getValue() { return 42; }
void example() {
    const int a = 10;
    const int b = getValue();   // OK at runtime
    int arr1[a];                // OK: a is a compile-time constant
    int arr2[b];                // Error: b is runtime
    constexpr int c = 10;
    // constexpr int d = getValue(); // error
    std::array<int, c> arr3;    // OK
}

Use constexpr when you need constant expressions (sizes, templates, static_assert). Use const for “immutable after first assignment” at runtime.

5. C++14/20 extensions

C++14

  • Multiple returns, loops, local variables in constexpr functions.
  • More constexpr member functions.

C++20

  • More operations allowed (including dynamic allocation in some contexts).
  • consteval: must be compile-time only.
  • Much of the standard library becomes constexpr-friendly.

if constexpr

Only the true branch is instantiated when the condition is a compile-time constant—handy for templates. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template <typename T>
auto unwrap(T x) {
    if constexpr (std::is_pointer_v<T>)
        return *x;
    else
        return x;
}

6. Common errors and fixes

Error 1: Non-literal types in constexpr

Cause: constexpr values/functions only deal with literal types. std::string and std::vector were not literal types before C++20. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// Error in C++17 and below
constexpr std::string msg = "hello";
// C++20: std::string can be constexpr in some contexts
// C++17 and below: fixed-size array
constexpr char msg[] = "hello";

Error 2: Runtime-only operations

Cause: In C++14 and below, dynamic allocation, exceptions, and virtual calls inside constexpr were restricted. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

constexpr int bad() {
    int* p = new int(42);
    return *p;
}
constexpr int good(int x) {
    return x * 2;
}

Error 3: Non-constant arguments to constexpr functions

Cause: If arguments are not constant expressions, the function may run at runtime—that is intended. Using the result where a constant expression is required then fails. 아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

constexpr int add(int a, int b) { return a + b; }
int main() {
    int x = 3, y = 5;
    // constexpr int z = add(x, y);  // error
    int w = add(x, y);
    constexpr int v = add(3, 5);
    return 0;
}

Error 4: Recursion depth exceeded

Use an iterative version or a lookup table instead of very deep recursive constexpr.

Error 5: Returning pointers/references to locals

In C++14 and below, returning addresses of locals from constexpr functions is problematic—prefer returning by value.

Error 6: static_assert with runtime values

Use templates or constexpr validation functions; static_assert needs a compile-time boolean.

Error 7: std::array size not a constant expression

get_size() must be constexpr to use std::array<int, get_size()>.

Error 8: Language version mismatch

C++11 constexpr bodies were very restricted; C++11-compatible factorial uses a single return with recursion instead of a loop.

Error 9: I/O or global state in constexpr

No std::cout, file I/O, or mutating globals during constexpr evaluation.

Error 10: Compiler recursion limits

GCC/Clang limit constexpr recursion depth; use -fconstexpr-depth=N or iterative code.

7. Performance benchmarks

Compile time vs runtime

Precomputing with constexpr gives zero runtime cost for that value. A full example compares factorialRuntime in a loop vs a constexpr precomputed result (typically ~50–200 ms vs ~1–5 ms for 1M iterations—environment-dependent).

CRC32: runtime init vs constexpr table

A full benchmark compares init_crc32_table() at startup against a constexpr CRC32_TABLE; the constexpr table avoids initialization delay at program start.

nextPowerOfTwo and memory

Aspectstd::array (constexpr size)std::vector (runtime size)
AllocationOften stack/staticHeap
LocalityBetterIndirection

8. Production patterns

Pattern 1: Compile-time lookup tables

Parity bits, CRC, encoding tables—build with std::make_index_sequence.

Pattern 2: Parse config from string literals

아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

constexpr int parseSize(const char* s) {
    int result = 0;
    while (*s >= '0' && *s <= '9') {
        result = result * 10 + (*s - '0');
        ++s;
    }
    return result;
}
constexpr int BUF_SIZE = parseSize("4096");
std::array<char, BUF_SIZE> buffer;

Pattern 3: Per-type constants via templates

Use template<typename T> struct TypeTraits with static constexpr size_t max_digits.

Pattern 4: Safe buffer sizes with next power of two

Pattern 5: Protocol buffer size = header + payload

Pattern 6: consteval (C++20)

Forces compile-time-only evaluation—runtime calls are ill-formed.

Pattern 7: Compile-time type id (debugging)

__PRETTY_FUNCTION__ / __FUNCSIG__ in static constexpr strings.

Pattern 8: static_assert with constexpr validators


9. Practical use

Array sizes

nextPowerOfTwo(100) → 128 when n is a constant expression; use for std::array without template metaprogramming for the same effect.

Replacing template metaprogramming

Prefer constexpr int fib(int n) over template<int N> struct Fib for readability; keep Fib<N>::value via std::integral_constant when needed.



Keywords

C++ constexpr, compile-time constant, constexpr function, constexpr vs const, literal type, if constexpr, consteval.

Summary

TopicContent
constexpr variablesCompile-time constants
constexpr functionsCallable in constant expressions when args are constant
constexpr constructorsEnable user-defined literal types
const vs constexprImmutable vs compile-time known
C++14Loops and multi-statement bodies
C++20Broader constexpr, consteval
if constexprCompile-time branches in templates

FAQ

When do I use this in practice?

Whenever sizes, buffers, tables, or validation must be known at build time—use constexpr; use const only for runtime immutability.

Isn’t constexpr slow?

Compile-time evaluation has zero runtime cost when fully folded; variable arguments execute like normal functions.

constexpr vs macros?

Macros are text substitution; constexpr is typed, debuggable, overloadable, and composes with templates.

Does constexpr slow builds?

Heavy constexpr (huge tables, deep recursion) can increase compile time—use judiciously and profile build times.

One-line summary: Use constexpr to compute constants, array sizes, and template arguments at compile time. Next: compile-time programming #26-2. Next: [C++ Hands-On #26-2] Compile-time programming: templates and constexpr Previous: [C++ Hands-On #25-3] Custom ranges

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3