C++20 consteval 완벽 가이드 | 컴파일 타임 전용 함수
이 글의 핵심
C++20 consteval 완벽 가이드에 대한 실전 가이드입니다. 컴파일 타임 전용 함수 등을 예제와 함께 상세히 설명합니다.
consteval이란? 왜 필요한가
문제 시나리오: constexpr의 모호함
문제: constexpr 함수는 컴파일 타임에도, 런타임에도 실행될 수 있습니다. 컴파일 타임 계산을 강제하고 싶을 때 모호합니다.
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int x = factorial(5); // 컴파일 타임: 120
int n = 10;
int y = factorial(n); // 런타임 (의도하지 않았을 수도)
}
해결: consteval은 컴파일 타임 전용 함수입니다. 런타임 값을 넘기면 컴파일 에러가 나므로, 컴파일 타임 계산을 보장할 수 있습니다.
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은 런타임 값
}
flowchart TD
subgraph constexpr["constexpr"]
ce1["컴파일 타임 OK"]
ce2["런타임 OK"]
end
subgraph consteval["consteval"]
cv1["컴파일 타임 OK"]
cv2["런타임 Error"]
end
목차
- constexpr vs consteval
- 즉시 함수 (Immediate Function)
- 실전 활용: 컴파일 타임 검증
- 메타프로그래밍
- 자주 발생하는 문제와 해결법
- 프로덕션 패턴
- 완전한 예제: 컴파일 타임 설정 시스템
1. constexpr vs consteval
비교
| 항목 | constexpr | consteval |
|---|---|---|
| 컴파일 타임 실행 | 가능 | 필수 |
| 런타임 실행 | 가능 | 불가 |
| 용도 | 유연한 계산 | 컴파일 타임 보장 |
| 에러 | 런타임 값 OK | 런타임 값 Error |
예시
// constexpr: 둘 다 가능
constexpr int add(int a, int b) {
return a + b;
}
int main() {
constexpr int x = add(1, 2); // 컴파일 타임
int a = 5;
int y = add(a, 10); // 런타임 (OK)
}
// consteval: 컴파일 타임만
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는 런타임 값
}
2. 즉시 함수
즉시 함수란
consteval 함수는 즉시 함수라고 하며, 호출 시점에 즉시 평가되어야 합니다.
consteval int square(int x) {
return x * x;
}
// consteval 함수는 consteval 내에서만 호출 가능
consteval int sum_of_squares(int a, int b) {
return square(a) + square(b); // OK
}
// 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. 실전 활용: 컴파일 타임 검증
범위 검증
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
}
문자열 해시
#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())) { // 컴파일 타임 해시
case hash("login"):
std::cout << "Login\n";
break;
case hash("logout"):
std::cout << "Logout\n";
break;
case hash("data"):
std::cout << "Data\n";
break;
}
}
타입 크기 검증
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는 16바이트 이하 보장
}
int main() {
process(42); // OK: sizeof(int) = 4
process(3.14); // OK: sizeof(double) = 8
// process(std::string{"hello"}); // Error: sizeof(string) > 16
}
4. 메타프로그래밍
컴파일 타임 팩토리얼
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
// 배열 크기로 사용
int buffer[factorial(4)]; // 24 elements
}
컴파일 타임 문자열 처리
#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
}
컴파일 타임 설정
consteval int get_buffer_size() {
#ifdef LARGE_BUFFER
return 1024 * 1024; // 1MB
#else
return 4096; // 4KB
#endif
}
consteval int get_thread_count() {
return 8; // 컴파일 타임 상수
}
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. 자주 발생하는 문제와 해결법
문제 1: 런타임 값 전달
증상: 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는 런타임 값
// ✅ 해결법 1: constexpr 변수
constexpr int c = 5;
int d = square(c); // OK
// ✅ 해결법 2: 리터럴
int e = square(5); // OK
}
문제 2: 부작용
증상: error: call to non-constexpr function.
원인: consteval 함수 안에서 I/O, 동적 할당 등 부작용이 있는 함수 호출.
// ❌ 잘못된 사용
consteval int bad() {
std::cout << "Hello\n"; // Error: I/O
return 42;
}
// ✅ 올바른 사용: 순수 계산만
consteval int good(int x) {
return x * 2;
}
문제 3: constexpr에서 consteval 호출
원인: constexpr 함수는 런타임에도 실행될 수 있으므로, consteval 함수를 호출할 수 없습니다.
consteval int immediate() {
return 42;
}
// ❌ 잘못된 사용
constexpr int wrapper() {
return immediate(); // Error
}
// ✅ 올바른 사용: consteval에서 호출
consteval int wrapper2() {
return immediate(); // OK
}
// ✅ 또는 constexpr if로 분기
constexpr int wrapper3(int x) {
if (std::is_constant_evaluated()) {
return 42; // 컴파일 타임
} else {
return x * 2; // 런타임
}
}
6. 프로덕션 패턴
패턴 1: 컴파일 타임 문자열 검증
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
}
패턴 2: 컴파일 타임 룩업 테이블
#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;
}
패턴 3: 설정 계산
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초
}
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. 완전한 예제: 컴파일 타임 설정 시스템
#include <string_view>
#include <array>
// 설정 키-값 쌍
struct ConfigEntry {
std::string_view key;
int value;
};
// 컴파일 타임 설정 생성
consteval auto generate_config() {
std::array<ConfigEntry, 3> config{{
{"max_connections", 1000},
{"timeout_ms", 30000},
{"buffer_size", 4096}
}};
return config;
}
// 컴파일 타임 설정 조회
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);
}
정리
| 개념 | 설명 |
|---|---|
| consteval | 컴파일 타임 전용 함수 |
| 즉시 함수 | 호출 시점에 즉시 평가 |
| constexpr 차이 | consteval은 런타임 실행 불가 |
| 용도 | 컴파일 타임 검증, 메타프로그래밍 |
consteval은 컴파일 타임 계산을 강제해, 런타임 오버헤드 없이 복잡한 계산을 수행할 수 있습니다.
FAQ
Q1: consteval vs constexpr 언제 쓰나요?
A: 컴파일 타임 계산을 강제하고 싶으면 consteval, 유연하게 둘 다 허용하려면 constexpr을 사용하세요.
Q2: consteval 함수를 constexpr에서 호출할 수 있나요?
A: 아니요. consteval 함수는 consteval 함수 안에서만 호출할 수 있습니다. constexpr 함수는 런타임에도 실행될 수 있으므로, consteval을 호출하면 에러입니다.
Q3: 런타임 값을 전달하면?
A: 컴파일 에러가 납니다. consteval 함수는 모든 인자가 컴파일 타임 상수여야 합니다.
Q4: 부작용이 있으면?
A: I/O, 동적 할당, 전역 변수 수정 등 부작용이 있는 코드는 consteval 함수에서 사용할 수 없습니다. 순수 계산만 가능합니다.
Q5: 컴파일러 지원은?
A:
- GCC 10+: 완전 지원
- Clang 10+: 완전 지원
- MSVC 2019 (16.10+): 완전 지원
Q6: consteval 학습 리소스는?
A:
- cppreference - consteval
- “C++20: The Complete Guide” by Nicolai Josuttis
- Compile-time programming in C++20
한 줄 요약: consteval로 컴파일 타임 계산을 강제할 수 있습니다. 다음으로 constexpr를 읽어보면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ static_assert | “정적 단언” 가이드
- C++20 Concepts 완벽 가이드 | 템플릿 제약의 새 시대
관련 글
- C++ constexpr 함수 |