C++ Compile-Time Programming Complete Guide | constexpr
이 글의 핵심
Master C++ compile-time programming: constexpr, consteval, if constexpr, TMP, performance benchmarks, and practical patterns for zero-overhead runtime.
Introduction
C++ compile-time programming uses constexpr, consteval, and if constexpr to compute at compile time, eliminating runtime overhead. Analogy: Runtime computation is like cooking on order; compile-time computation is like pre-cooking and reheating—execution is instant.
What You’ll Learn
- Understand differences between constexpr, consteval, and if constexpr
- Distinguish compile-time vs runtime computation
- Compare with template metaprogramming (TMP)
- Apply practical patterns and avoid pitfalls
constexpr Basics
constexpr Variables
#include <iostream>
constexpr int MAX_SIZE = 100;
constexpr double PI = 3.14159;
int main() {
int arr[MAX_SIZE]; // Array size must be compile-time constant
std::cout << PI << std::endl;
return 0;
}
Key: constexpr variables can be used as array sizes and template arguments.
constexpr Functions
#include <iostream>
// Can execute at compile time or runtime
constexpr int square(int x) {
return x * x;
}
int main() {
constexpr int result = square(5); // Compile-time
int arr[result]; // OK: compile-time constant
int x = 10;
int result2 = square(x); // Runtime (x is not constexpr)
std::cout << result << ", " << result2 << std::endl;
return 0;
}
Output:
25, 100
constexpr vs const
// const: runtime constant
const int x = 10;
// constexpr: compile-time constant
constexpr int y = 10;
int arr1[x]; // ❌ Error (const not guaranteed compile-time)
int arr2[y]; // ✅ OK (constexpr is compile-time)
Key: constexpr guarantees compile-time evaluation; const only guarantees immutability.
Practical Examples
1. constexpr Functions
#include <iostream>
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int fact5 = factorial(5); // 120 (compile-time)
std::cout << fact5 << std::endl;
return 0;
}
Output:
120
Key: Recursive constexpr functions are evaluated at compile time when arguments are constexpr.
2. constexpr Classes
#include <iostream>
class Point {
private:
int x_, y_;
public:
constexpr Point(int x, int y) : x_(x), y_(y) {}
constexpr int getX() const { return x_; }
constexpr int getY() const { return y_; }
constexpr int distanceSquared() const {
return x_ * x_ + y_ * y_;
}
};
int main() {
constexpr Point p(3, 4);
constexpr int dist = p.distanceSquared(); // 25 (compile-time)
int arr[dist]; // OK
std::cout << dist << std::endl;
return 0;
}
Output:
25
3. if constexpr (C++17)
#include <iostream>
#include <type_traits>
template<typename T>
auto getValue(T t) {
if constexpr (std::is_pointer_v<T>) {
return *t; // Dereference if pointer
} else {
return t; // Return as-is
}
}
int main() {
int x = 10;
int* ptr = &x;
std::cout << getValue(x) << std::endl; // 10
std::cout << getValue(ptr) << std::endl; // 10
return 0;
}
Output:
10
10
Key: if constexpr enables compile-time branching based on type traits, eliminating dead code paths.
4. consteval (C++20)
#include <iostream>
// Must execute at compile time only
consteval int sqr(int n) {
return n * n;
}
int main() {
constexpr int x = sqr(5); // ✅ OK (compile-time)
int y = 10;
// int z = sqr(y); // ❌ Error: runtime value
std::cout << x << std::endl;
return 0;
}
Output:
25
Key: consteval enforces compile-time-only execution, preventing runtime calls.
Advanced Patterns
1. Compile-Time String Hashing
#include <iostream>
constexpr unsigned int hash(const char* str) {
unsigned int hash = 5381;
while (*str) {
hash = ((hash << 5) + hash) + (*str++);
}
return hash;
}
int main() {
constexpr unsigned int startHash = hash("start");
constexpr unsigned int stopHash = hash("stop");
const char* command = "start";
switch (hash(command)) {
case startHash:
std::cout << "Start" << std::endl;
break;
case stopHash:
std::cout << "Stop" << std::endl;
break;
default:
std::cout << "Unknown" << std::endl;
}
return 0;
}
Output:
Start
Key: Compile-time hashing enables fast string-based switch statements.
2. Compile-Time Array Generation
#include <array>
#include <iostream>
template<size_t N>
constexpr auto generateFibonacci() {
std::array<int, N> result{};
if (N > 0) result[0] = 0;
if (N > 1) result[1] = 1;
for (size_t i = 2; i < N; ++i) {
result[i] = result[i - 1] + result[i - 2];
}
return result;
}
int main() {
constexpr auto fib = generateFibonacci<10>();
for (int x : fib) {
std::cout << x << " "; // 0 1 1 2 3 5 8 13 21 34
}
std::cout << std::endl;
return 0;
}
Output:
0 1 1 2 3 5 8 13 21 34
Key: Compile-time array generation creates lookup tables with zero runtime cost.
3. Template Metaprogramming (TMP)
#include <iostream>
// Recursive template
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N - 1>::value;
};
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
int main() {
std::cout << Factorial<5>::value << std::endl; // 120 (compile-time)
return 0;
}
Output:
120
Key: TMP uses template recursion for compile-time computation. Prefer constexpr functions for readability.
Performance Comparison
Benchmark: Fibonacci
#include <chrono>
#include <iostream>
// Runtime
int runtimeFib(int n) {
if (n <= 1) return n;
return runtimeFib(n - 1) + runtimeFib(n - 2);
}
// Compile-time
constexpr int compiletimeFib(int n) {
if (n <= 1) return n;
return compiletimeFib(n - 1) + compiletimeFib(n - 2);
}
int main() {
// Runtime (slow)
auto start = std::chrono::high_resolution_clock::now();
int r = runtimeFib(40);
auto end = std::chrono::high_resolution_clock::now();
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
std::cout << "Runtime: " << time << "ms" << std::endl;
// Compile-time (instant)
constexpr int c = compiletimeFib(40);
std::cout << "Compile-time: 0ms (pre-computed)" << std::endl;
return 0;
}
Results:
| Method | Time |
|---|---|
| Runtime | 1200ms |
| Compile-time | 0ms |
| Conclusion: Compile-time computation is dramatically faster at runtime (but increases build time). |
Production Patterns
Pattern 1: Lookup Table Generation
#include <array>
#include <iostream>
#include <cmath>
constexpr auto generateSinTable() {
constexpr size_t SIZE = 360;
std::array<double, SIZE> table{};
constexpr double PI = 3.14159265358979323846;
for (size_t i = 0; i < SIZE; ++i) {
double radians = i * PI / 180.0;
// In real code: table[i] = std::sin(radians);
// (std::sin is not constexpr in C++17, but is in C++26)
table[i] = radians;
}
return table;
}
int main() {
constexpr auto sinTable = generateSinTable();
std::cout << sinTable[45] << std::endl; // 0.785398 (45 degrees)
return 0;
}
Key: Pre-compute trigonometric tables at compile time for real-time graphics/physics.
Pattern 2: Type Checking
#include <iostream>
#include <type_traits>
template<typename T>
constexpr bool isNumeric() {
if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>) {
return true;
} else {
return false;
}
}
template<typename T>
void process(T value) {
if constexpr (isNumeric<T>()) {
std::cout << "Number: " << value << std::endl;
} else {
std::cout << "String: " << value << std::endl;
}
}
int main() {
process(42); // Number: 42
process("hello"); // String: hello
return 0;
}
Output:
Number: 42
String: hello
Pattern 3: Compile-Time Sorting
#include <algorithm>
#include <array>
#include <iostream>
constexpr void bubbleSort(int* arr, int n) {
for (int i = 0; i < n - 1; ++i) {
for (int j = 0; j < n - i - 1; ++j) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
constexpr auto getSortedArray() {
std::array<int, 5> arr = {5, 2, 8, 1, 9};
bubbleSort(arr.data(), arr.size());
return arr;
}
int main() {
constexpr auto sorted = getSortedArray();
for (int x : sorted) {
std::cout << x << " "; // 1 2 5 8 9
}
std::cout << std::endl;
return 0;
}
Output:
1 2 5 8 9
Pattern 4: Bit Manipulation
#include <iostream>
constexpr unsigned int reverseBits(unsigned int n) {
unsigned int result = 0;
for (int i = 0; i < 32; ++i) {
result <<= 1;
result |= (n & 1);
n >>= 1;
}
return result;
}
int main() {
constexpr unsigned int reversed = reverseBits(0b10110000);
std::cout << std::hex << reversed << std::endl;
return 0;
}
Key: Compile-time bit manipulation for embedded systems and cryptography.
Common Issues
Issue 1: constexpr Constraints
// ❌ Static variables not allowed (pre-C++23)
constexpr int bad() {
static int x = 0; // Error
return x++;
}
// ❌ Dynamic allocation not allowed (C++17)
constexpr int bad2() {
int* p = new int(10); // Error
return *p;
}
// ✅ OK
constexpr int good(int x) {
return x * 2;
}
Key: constexpr functions have restrictions—no static locals (pre-C++23), limited dynamic allocation.
Issue 2: Runtime Values
#include <iostream>
int main() {
int x;
std::cin >> x;
// ❌ Error: runtime value
constexpr int y = x * 2;
// ✅ OK: runtime variable
int y2 = x * 2;
return 0;
}
Key: constexpr requires compile-time known values.
Issue 3: Complex Computation
// ❌ Slow compile time
constexpr int slowFib(int n) {
if (n <= 1) return n;
return slowFib(n - 1) + slowFib(n - 2);
}
constexpr int x = slowFib(50); // Very slow compilation!
// ✅ Memoization
constexpr auto fastFib() {
std::array<int, 50> result{};
result[0] = 0;
result[1] = 1;
for (int i = 2; i < 50; ++i) {
result[i] = result[i - 1] + result[i - 2];
}
return result;
}
constexpr auto fibTable = fastFib();
constexpr int y = fibTable[49]; // Fast
Key: Use iterative algorithms or memoization to avoid exponential compile-time complexity.
Issue 4: constexpr vs consteval Confusion
// constexpr: compile-time or runtime
constexpr int square(int n) {
return n * n;
}
int x = 10;
int y = square(x); // ✅ Runs at runtime
// consteval: compile-time only
consteval int sqr(int n) {
return n * n;
}
constexpr int z = sqr(5); // ✅ OK
// int w = sqr(x); // ❌ Error: runtime value
Key: Use consteval to enforce compile-time-only intent.
Template Metaprogramming (TMP)
Recursive Templates
#include <iostream>
// Recursive template
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N - 1>::value;
};
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
int main() {
std::cout << Factorial<5>::value << std::endl; // 120 (compile-time)
return 0;
}
Output:
120
Key: TMP uses template specialization for compile-time recursion. Prefer constexpr functions for readability.
TMP vs constexpr
// TMP (verbose)
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N - 1>::value;
};
// constexpr (readable)
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
Recommendation: Use constexpr functions for most compile-time computation; reserve TMP for type-level programming.
Production Use Cases
Use Case 1: Configuration Tables
#include <array>
constexpr auto generateConfigTable() {
std::array<int, 256> table{};
for (int i = 0; i < 256; ++i) {
table[i] = i * 2; // Example: multiply by 2
}
return table;
}
constexpr auto configTable = generateConfigTable();
int main() {
int value = configTable[42]; // Instant lookup
}
Key: Pre-compute configuration tables for embedded systems.
Use Case 2: Type Dispatch
#include <iostream>
#include <type_traits>
template<typename T>
void process(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "Integer: " << value << std::endl;
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "Float: " << value << std::endl;
} else {
std::cout << "Other: " << value << std::endl;
}
}
int main() {
process(42); // Integer: 42
process(3.14); // Float: 3.14
process("hello"); // Other: hello
return 0;
}
Output:
Integer: 42
Float: 3.14
Other: hello
Use Case 3: Compile-Time Validation
#include <array>
template<size_t N>
constexpr bool isPowerOfTwo() {
return N > 0 && (N & (N - 1)) == 0;
}
template<size_t N>
class Buffer {
static_assert(isPowerOfTwo<N>(), "Buffer size must be power of 2");
std::array<char, N> data;
};
int main() {
Buffer<256> buf1; // ✅ OK
// Buffer<100> buf2; // ❌ Error: not power of 2
}
Key: Use static_assert with constexpr functions for compile-time validation.
Performance Benchmarks
Compile-Time vs Runtime
// Benchmark: Fibonacci(40)
// Runtime: 1200ms
// Compile-time: 0ms (pre-computed)
Key: Compile-time computation eliminates runtime overhead but increases build time.
Compile Time Impact
// Simple constexpr: +0.1s compile time
constexpr int x = factorial(10);
// Complex constexpr: +5s compile time
constexpr auto table = generateLargeTable<10000>();
Trade-off: Balance compile-time work with build time constraints.
Best Practices
1. Use constexpr for Pure Functions
// ✅ Pure function: good constexpr candidate
constexpr int add(int a, int b) {
return a + b;
}
// ❌ Side effects: not constexpr
int addAndLog(int a, int b) {
std::cout << "Adding" << std::endl; // I/O not allowed
return a + b;
}
2. Prefer constexpr Over TMP
// ❌ TMP (verbose)
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N - 1>::value;
};
// ✅ constexpr (readable)
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
3. Use consteval for Compile-Time-Only Intent
// ✅ Enforce compile-time
consteval int configValue(int id) {
return id * 100;
}
constexpr int x = configValue(5); // OK
// int y = configValue(runtimeValue); // Error
4. Avoid Deep Recursion
// ❌ Deep recursion (slow compile)
constexpr int fib(int n) {
return n <= 1 ? n : fib(n-1) + fib(n-2);
}
// ✅ Iterative (fast compile)
constexpr int fib(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
int temp = a + b;
a = b;
b = temp;
}
return a;
}
5. Use static_assert for Documentation
template<typename T>
void process(T value) {
static_assert(std::is_arithmetic_v<T>, "T must be numeric");
// ...
}
Summary
Key Points
- constexpr
- Compile-time or runtime execution
- Variables, functions, classes
- Array sizes, template arguments
- consteval (C++20)
- Compile-time-only execution
- Immediate functions
- No runtime values allowed
- if constexpr (C++17)
- Compile-time branching
- Type-based conditional compilation
- Replaces template specialization
- Performance
- Compile-time: zero runtime overhead
- Compile time may increase
- Use memoization for complex computation
Selection Guide
| Scenario | Recommendation | Reason |
|---|---|---|
| Compile-time constant | constexpr | Array sizes, etc. |
| Compile-time-only | consteval | Clear intent |
| Type-based branching | if constexpr | Conditional compilation |
| Runtime also needed | constexpr | Flexibility |
Cheat Sheet
// constexpr variable
constexpr int MAX_SIZE = 100;
// constexpr function
constexpr int square(int x) {
return x * x;
}
// constexpr class
class Point {
public:
constexpr Point(int x, int y) : x_(x), y_(y) {}
constexpr int getX() const { return x_; }
private:
int x_, y_;
};
// if constexpr
template<typename T>
auto getValue(T t) {
if constexpr (std::is_pointer_v<T>) {
return *t;
} else {
return t;
}
}
// consteval
consteval int sqr(int n) {
return n * n;
}
Compile-Time Programming Checklist
- Pure functions marked constexpr?
- Using consteval for compile-time-only intent?
- Avoiding deep recursion?
- Using static_assert for validation?
- Balancing compile time vs runtime performance?
Related Articles
- C++ constexpr Functions Guide
- C++ if constexpr Guide
- C++ constexpr Basics Series #26-1
- C++ Compile-Time Optimization #15-3
Keywords
C++ constexpr, consteval, compile-time programming, template metaprogramming, TMP, if constexpr, immediate functions One-line summary: Compile-time programming with constexpr, consteval, and if constexpr eliminates runtime overhead by pre-computing values and enabling type-based conditional compilation.
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Master C++ compile-time programming: constexpr variables/functions/classes, consteval immediate functions, if.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ constexpr Lambda | ‘컴파일 타임 람다’ 가이드
- C++ constexpr 함수 | ‘컴파일 타임 함수’ 가이드
- C++ constexpr if | ‘컴파일 타임 분기’ 가이드
Keywords Covered in This Article (Related Search Terms)
This article covers C++, constexpr, compile-time, TMP, metaprogramming, consteval, if constexpr.