Complete Guide to C++20 consteval | Compile-Time Only Functions
이 글의 핵심
consteval immediate functions: must run at compile time—stricter than constexpr for APIs that must never execute at runtime.
What is consteval? Why do we need it?
Problem Scenario: The ambiguity of constexpr
Problem: A constexpr function can execute at compile-time or runtime, which makes it ambiguous when you want to enforce compile-time calculations.
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int x = factorial(5); // Compile-time: 120
int n = 10;
int y = factorial(n); // Runtime (possibly unintended)
}
Solution: consteval ensures that a function is compile-time only. Passing runtime values to it results in a compile error, guaranteeing compile-time evaluation.
consteval int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int x = factorial(5); // OK: 120
int n = 10;
// int y = factorial(n); // Error: n is a runtime value
}
flowchart TD
subgraph constexpr["constexpr"]
ce1["Compile-time OK"]
ce2["Runtime OK"]
end
subgraph consteval["consteval"]
cv1["Compile-time OK"]
cv2["Runtime Error"]
end
Table of Contents
- constexpr vs consteval
- Immediate Functions
- Practical Applications: Compile-Time Validation
- Metaprogramming
- Common Errors and Solutions
- Production Patterns
- Complete Example: Compile-Time Configuration System
1. constexpr vs consteval
Comparison
| Feature | constexpr | consteval |
|---|---|---|
| Compile-time execution | Possible | Mandatory |
| Runtime execution | Possible | Not allowed |
| Use case | Flexible calculations | Guaranteed compile-time |
| Errors | Runtime values OK | Runtime values cause error |
Examples
// constexpr: Both compile-time and runtime allowed
constexpr int add(int a, int b) {
return a + b;
}
int main() {
constexpr int x = add(1, 2); // Compile-time
int a = 5;
int y = add(a, 10); // Runtime (OK)
}
// consteval: Compile-time only
consteval int multiply(int a, int b) {
return a * b;
}
int main() {
constexpr int x = multiply(2, 3); // OK
int a = 5;
// int y = multiply(a, 10); // Error: a is a runtime value
}
2. Immediate Functions
What is an Immediate Function?
A consteval function is referred to as an immediate function, meaning it must be evaluated immediately at the point of invocation.
consteval int square(int x) {
return x * x;
}
// consteval functions can only be called within consteval functions
consteval int sum_of_squares(int a, int b) {
return square(a) + square(b); // OK
}
// Cannot call consteval from constexpr
constexpr int wrapper(int x) {
// return square(x); // Error: consteval in constexpr
return x * 2;
}
int main() {
constexpr int result = sum_of_squares(3, 4); // 25
}
3. Practical Applications: Compile-Time Validation
Range Validation
consteval int check_range(int value, int min, int max) {
if (value < min || value > max) {
throw "Value out of range";
}
return value;
}
int main() {
constexpr int size = check_range(100, 1, 1024); // OK
int buffer[size];
// constexpr int bad = check_range(2000, 1, 1024); // Compile error
}
String Hashing
#include <string_view>
consteval unsigned int hash(std::string_view str) {
unsigned int hash = 5381;
for (char c : str) {
hash = ((hash << 5) + hash) + static_cast<unsigned char>(c);
}
return hash;
}
enum class MessageType : unsigned int {
Login = hash("login"),
Logout = hash("logout"),
Data = hash("data")
};
void handle_message(const std::string& type) {
switch (hash(type.c_str())) { // Compile-time hash
case hash("login"):
std::cout << "Login\n";
break;
case hash("logout"):
std::cout << "Logout\n";
break;
case hash("data"):
std::cout << "Data\n";
break;
}
}
Type Size Validation
template<typename T>
consteval bool is_small_type() {
return sizeof(T) <= 16;
}
template<typename T>
requires is_small_type<T>()
void process(T value) {
// T is guaranteed to be 16 bytes or less
}
int main() {
process(42); // OK: sizeof(int) = 4
process(3.14); // OK: sizeof(double) = 8
// process(std::string{"hello"}); // Error: sizeof(string) > 16
}
4. Metaprogramming
Compile-Time Factorial
consteval int factorial(int n) {
if (n < 0) throw "Negative factorial";
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int f5 = factorial(5); // 120
constexpr int f10 = factorial(10); // 3628800
// Use as array size
int buffer[factorial(4)]; // 24 elements
}
Compile-Time String Processing
#include <array>
#include <string_view>
consteval std::size_t count_chars(std::string_view str, char c) {
std::size_t count = 0;
for (char ch : str) {
if (ch == c) ++count;
}
return count;
}
int main() {
constexpr auto count = count_chars("hello world", 'l');
static_assert(count == 3);
std::array<char, count> buffer; // 3 elements
}
Compile-Time Configuration
consteval int get_buffer_size() {
#ifdef LARGE_BUFFER
return 1024 * 1024; // 1MB
#else
return 4096; // 4KB
#endif
}
consteval int get_thread_count() {
return 8; // Compile-time constant
}
int main() {
constexpr int buffer_size = get_buffer_size();
constexpr int threads = get_thread_count();
char buffer[buffer_size];
std::array<std::thread, threads> thread_pool;
}
5. Common Errors and Solutions
Issue 1: Passing Runtime Values
Symptom: error: call to consteval function is not a constant expression.
consteval int square(int x) {
return x * x;
}
int main() {
int a = 5;
// int b = square(a); // Error: a is a runtime value
// ✅ Solution 1: Use constexpr variables
constexpr int c = 5;
int d = square(c); // OK
// ✅ Solution 2: Use literals
int e = square(5); // OK
}
Issue 2: Side Effects
Symptom: error: call to non-constexpr function.
Cause: consteval functions cannot perform side effects like I/O or dynamic memory allocation.
// ❌ Incorrect usage
consteval int bad() {
std::cout << "Hello\n"; // Error: I/O
return 42;
}
// ✅ Correct usage: Pure calculations only
consteval int good(int x) {
return x * 2;
}
Issue 3: Calling consteval from constexpr
Cause: constexpr functions can execute at runtime, so calling consteval functions from them is not allowed.
consteval int immediate() {
return 42;
}
// ❌ Incorrect usage
constexpr int wrapper() {
return immediate(); // Error
}
// ✅ Correct usage: Call within consteval
consteval int wrapper2() {
return immediate(); // OK
}
// ✅ Or use constexpr if for branching
constexpr int wrapper3(int x) {
if (std::is_constant_evaluated()) {
return 42; // Compile-time
} else {
return x * 2; // Runtime
}
}
6. Production Patterns
Pattern 1: Compile-Time String Validation
consteval bool is_valid_identifier(std::string_view str) {
if (str.empty()) return false;
if (!std::isalpha(str[0]) && str[0] != '_') return false;
for (char c : str) {
if (!std::isalnum(c) && c != '_') return false;
}
return true;
}
template<std::size_t N>
consteval auto make_identifier(const char (&str)[N]) {
if (!is_valid_identifier(str)) {
throw "Invalid identifier";
}
return std::string_view(str, N - 1);
}
int main() {
constexpr auto id1 = make_identifier("valid_name"); // OK
// constexpr auto id2 = make_identifier("123invalid"); // Compile error
}
Pattern 2: Compile-Time Lookup Table
#include <array>
consteval std::array<int, 256> generate_crc_table() {
std::array<int, 256> table{};
for (int i = 0; i < 256; ++i) {
int crc = i;
for (int j = 0; j < 8; ++j) {
crc = (crc >> 1) ^ ((crc & 1) ? 0xEDB88320 : 0);
}
table[i] = crc;
}
return table;
}
constexpr auto CRC_TABLE = generate_crc_table();
unsigned int crc32(const char* data, std::size_t length) {
unsigned int crc = 0xFFFFFFFF;
for (std::size_t i = 0; i < length; ++i) {
crc = (crc >> 8) ^ CRC_TABLE[(crc ^ data[i]) & 0xFF];
}
return ~crc;
}
Pattern 3: Configuration Calculations
consteval int calculate_pool_size() {
#ifdef PRODUCTION
return 128;
#elif defined(STAGING)
return 64;
#else
return 16;
#endif
}
consteval int calculate_timeout_ms() {
return 30 * 1000; // 30 seconds
}
int main() {
constexpr int pool_size = calculate_pool_size();
constexpr int timeout = calculate_timeout_ms();
static_assert(pool_size > 0);
static_assert(timeout > 0);
}
7. Complete Example: Compile-Time Configuration System
#include <string_view>
#include <array>
// Configuration key-value pairs
struct ConfigEntry {
std::string_view key;
int value;
};
// Generate compile-time configuration
consteval auto generate_config() {
std::array<ConfigEntry, 3> config{{
{"max_connections", 1000},
{"timeout_ms", 30000},
{"buffer_size", 4096}
}};
return config;
}
// Retrieve compile-time configuration
consteval int get_config(std::string_view key) {
constexpr auto config = generate_config();
for (const auto& entry : config) {
if (entry.key == key) {
return entry.value;
}
}
throw "Config key not found";
}
int main() {
constexpr int max_conn = get_config("max_connections"); // 1000
constexpr int timeout = get_config("timeout_ms"); // 30000
std::array<int, max_conn> connection_pool;
static_assert(max_conn == 1000);
static_assert(timeout == 30000);
}
Summary
| Concept | Description |
|---|---|
| consteval | Compile-time only functions |
| Immediate Functions | Evaluated immediately at call site |
| Difference from constexpr | consteval cannot execute at runtime |
| Use cases | Compile-time validation, metaprogramming |
consteval enforces compile-time calculations, enabling complex computations without runtime overhead.
FAQ
Q1: When should I use consteval vs constexpr?
A: Use consteval when you want to enforce compile-time calculations. Use constexpr for flexibility between compile-time and runtime.
Q2: Can a consteval function be called from a constexpr function?
A: No. consteval functions can only be called within other consteval functions. constexpr functions may execute at runtime, so calling consteval from them results in an error.
Q3: What happens if I pass runtime values?
A: A compile error occurs. All arguments to consteval functions must be compile-time constants.
Q4: What if the function has side effects?
A: Functions with side effects like I/O, dynamic allocation, or global variable modification cannot be used in consteval. Only pure calculations are allowed.
Q5: What is the compiler support for consteval?
A:
- GCC 10+: Fully supported
- Clang 10+: Fully supported
- MSVC 2019 (16.10+): Fully supported
Q6: Where can I learn more about consteval?
A:
- cppreference - consteval
- “C++20: The Complete Guide” by Nicolai Josuttis
- Compile-time programming in C++20
One-line summary: consteval enforces compile-time calculations. Next, check out constexpr for more details.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ static_assert | “정적 단언” 가이드
- C++20 Concepts 완벽 가이드 | 템플릿 제약의 새 시대
이 글에서 다루는 키워드 (관련 검색어)
C++, consteval, cpp20, compile-time, constexpr, metaprogramming 등으로 검색하시면 이 글이 도움이 됩니다.