constexpr·consteval·if constexpr — 컴파일 타임 프로그래밍 완전 가이드
개요 — 컴파일 타임 프로그래밍이란
Section titled “개요 — 컴파일 타임 프로그래밍이란”컴파일 타임 프로그래밍은 런타임이 아닌 컴파일 단계에서 계산을 수행하는 기법입니다. 결과가 컴파일 타임에 결정되므로 런타임 오버헤드가 없고, 값이 상수로 취급되어 최적화에 유리합니다.
C++ 컴파일 타임 도구:
constexpr— 컴파일 타임에 평가 가능한 함수·변수consteval— 반드시 컴파일 타임에만 평가되는 함수 (C++20)constinit— 컴파일 타임에 초기화됨을 보장 (C++20)if constexpr— 컴파일 타임 조건 분기 (C++17)
1. constexpr — 컴파일 타임 평가 가능
Section titled “1. constexpr — 컴파일 타임 평가 가능”1.1 constexpr 변수
Section titled “1.1 constexpr 변수”// 컴파일 타임 상수constexpr int MaxSize = 256;constexpr double Pi = 3.141592653589793;
// 배열 크기로 사용 가능 (런타임 변수는 불가)int buffer[MaxSize]; // OK — MaxSize는 컴파일 타임 상수1.2 constexpr 함수 (C++11)
Section titled “1.2 constexpr 함수 (C++11)”// constexpr 함수 — 상수 인자 → 컴파일 타임 계산// — 비상수 인자 → 런타임 계산 (일반 함수처럼)constexpr int Factorial(int n){ return (n <= 1) ? 1 : n * Factorial(n - 1);}
constexpr int Fact5 = Factorial(5); // 컴파일 타임: 120int n = 5;int FactN = Factorial(n); // 런타임: 120 (n이 런타임 값)
// 배열 크기, 템플릿 인자로 사용 가능static_assert(Fact5 == 120);int arr[Factorial(4)]; // int arr[24]1.3 C++14 — constexpr 함수 제약 완화
Section titled “1.3 C++14 — constexpr 함수 제약 완화”// C++11: 단일 return 문만 허용// C++14: 루프, 조건문, 지역 변수 허용constexpr int Sum(int n){ int result = 0; for (int i = 1; i <= n; ++i) result += i; return result;}
constexpr int Total = Sum(100); // 5050 — 컴파일 타임1.4 C++20 — constexpr의 추가 확장
Section titled “1.4 C++20 — constexpr의 추가 확장”// C++20: constexpr에서 virtual 함수, try-catch, dynamic_cast 허용struct Shape{ constexpr virtual double Area() const = 0; constexpr virtual ~Shape() = default;};
struct Circle : Shape{ double radius; constexpr explicit Circle(double r) : radius(r) {} constexpr double Area() const override { return 3.141592653589793 * radius * radius; }};
constexpr double GetArea(){ Circle c{5.0}; return c.Area();}
constexpr double area = GetArea(); // 컴파일 타임 계산2. consteval — 강제 컴파일 타임 함수 (C++20)
Section titled “2. consteval — 강제 컴파일 타임 함수 (C++20)”consteval로 선언된 함수는 반드시 컴파일 타임 문맥에서만 호출될 수 있습니다. 런타임 인자로 호출하면 컴파일 오류가 발생합니다.
// consteval: 항상 컴파일 타임 평가 강제consteval int CompileTimeSquare(int n){ return n * n;}
constexpr int a = CompileTimeSquare(5); // OK: 25 (컴파일 타임)// int x = 5;// int b = CompileTimeSquare(x); // 오류: x는 런타임 값
// constexpr vs consteval 비교constexpr int CxSquare(int n) { return n * n; }consteval int CeSquare(int n) { return n * n; }
int runtime_n = 5;int r1 = CxSquare(runtime_n); // OK: 런타임 실행// int r2 = CeSquare(runtime_n); // 오류!
// 활용: 리터럴 문자열 컴파일 타임 해시consteval uint32_t Hash(const char* str){ uint32_t hash = 2166136261u; while (*str) { hash ^= static_cast<uint32_t>(*str++); hash *= 16777619u; } return hash;}
constexpr auto id = Hash("PlayerCharacter"); // 컴파일 타임 상수3. constinit — 컴파일 타임 초기화 보장 (C++20)
Section titled “3. constinit — 컴파일 타임 초기화 보장 (C++20)”// constinit: 변수가 컴파일 타임에 초기화됨을 보장// 단, 이후에 수정은 가능 (const와 다름)constinit int GlobalCounter = 0; // OK// constinit int Bad = SomeRuntimeFunc(); // 오류
// 정적 초기화 순서 문제(Static Initialization Order Fiasco) 방지에 유용constinit static int CachedValue = 100;
void Update() { CachedValue = 200; } // 수정 가능 (constinit은 const가 아님)constexpr vs consteval vs constinit 비교
Section titled “constexpr vs consteval vs constinit 비교”| 키워드 | 평가 시점 | 수정 가능 | 적용 대상 |
|---|---|---|---|
constexpr 변수 | 컴파일 타임 | 불가 | 변수, 함수 |
constexpr 함수 | 컴파일 또는 런타임 | — | 함수 |
consteval 함수 | 반드시 컴파일 타임 | — | 함수 |
constinit 변수 | 컴파일 타임 초기화 | 가능 | 정적 변수 |
const 변수 | 컴파일 또는 런타임 | 불가 | 변수 |
4. if constexpr — 컴파일 타임 조건 분기 (C++17)
Section titled “4. if constexpr — 컴파일 타임 조건 분기 (C++17)”if constexpr는 조건이 컴파일 타임에 평가되며, false인 브랜치는 컴파일 자체가 되지 않습니다. 이는 일반 if와 결정적 차이입니다.
#include <type_traits>
template<typename T>auto Describe(T value){ if constexpr (std::is_integral_v<T>) { // 이 블록은 T가 정수일 때만 컴파일됨 return std::string("정수: ") + std::to_string(value); } else if constexpr (std::is_floating_point_v<T>) { return std::string("실수: ") + std::to_string(value); } else if constexpr (std::is_pointer_v<T>) { // T가 포인터가 아닌 경우 이 블록은 컴파일 안 됨 // value가 역참조 불가능해도 오류 없음 return std::string("포인터"); } else { return std::string("기타"); }}
auto s1 = Describe(42); // "정수: 42"auto s2 = Describe(3.14); // "실수: 3.140000"if constexpr vs SFINAE
Section titled “if constexpr vs SFINAE”// SFINAE 방식 — 복잡하고 가독성 낮음template<typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>std::string Format(T v) { return std::to_string(v); }
template<typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>std::string Format(T v) { return std::to_string(v) + "f"; }
// if constexpr 방식 — 단일 함수, 직관적template<typename T>std::string Format(T v){ if constexpr (std::is_integral_v<T>) return std::to_string(v); else if constexpr (std::is_floating_point_v<T>) return std::to_string(v) + "f"; else return "[unknown]";}5. 컴파일 타임 자료구조
Section titled “5. 컴파일 타임 자료구조”C++20에서는 constexpr 함수 내에서 std::vector, std::string, 동적 할당까지 허용됩니다 (단, 컴파일 타임 문맥 내에서만).
#include <array>#include <algorithm>
// 컴파일 타임 소수 계산 (에라토스테네스의 체)consteval auto SievePrimes(int limit){ std::vector<bool> is_prime(limit + 1, true); is_prime[0] = is_prime[1] = false; for (int i = 2; i * i <= limit; ++i) if (is_prime[i]) for (int j = i * i; j <= limit; j += i) is_prime[j] = false;
std::vector<int> primes; for (int i = 2; i <= limit; ++i) if (is_prime[i]) primes.push_back(i); return primes;}
// 컴파일 타임 정렬된 배열constexpr auto SortedPrimes = []() consteval { std::array<int, 10> arr = {29, 3, 7, 2, 5, 11, 13, 17, 19, 23}; std::sort(arr.begin(), arr.end()); return arr;}();
static_assert(SortedPrimes[0] == 2);static_assert(SortedPrimes[9] == 29);6. static_assert — 컴파일 타임 단언
Section titled “6. static_assert — 컴파일 타임 단언”// 타입 크기 검증static_assert(sizeof(int) == 4, "int는 4바이트여야 합니다");static_assert(sizeof(void*) == 8, "64비트 시스템에서 실행하세요");
// 템플릿 제약 검증template<typename T>class Buffer{ static_assert(std::is_trivially_copyable_v<T>, "Buffer는 trivially copyable 타입만 지원합니다"); T data[256];};
// 컴파일 타임 계산 결과 검증constexpr int N = Factorial(5);static_assert(N == 120, "Factorial(5)는 120이어야 함");7. 실전 패턴 — 룩업 테이블 생성
Section titled “7. 실전 패턴 — 룩업 테이블 생성”// 런타임마다 계산하는 대신 컴파일 타임에 테이블 생성consteval auto MakeSinTable(int size){ std::vector<double> table(size); constexpr double pi = 3.141592653589793; for (int i = 0; i < size; ++i) table[i] = std::sin(2.0 * pi * i / size); return table;}
// 컴파일 타임 CRC 테이블consteval auto MakeCRC32Table(){ std::array<uint32_t, 256> table{}; for (uint32_t i = 0; i < 256; ++i) { uint32_t crc = i; for (int j = 0; j < 8; ++j) crc = (crc & 1) ? (0xEDB88320u ^ (crc >> 1)) : (crc >> 1); table[i] = crc; } return table;}
constexpr auto CRC32Table = MakeCRC32Table();| 기능 | 버전 | 주요 용도 |
|---|---|---|
constexpr 변수 | C++11 | 컴파일 타임 상수 |
constexpr 함수 | C++11/14/17/20 | 컴파일 또는 런타임 계산 |
if constexpr | C++17 | 템플릿 내 조건 분기 |
consteval | C++20 | 강제 컴파일 타임 함수 |
constinit | C++20 | 정적 변수 초기화 순서 보장 |
static_assert | C++11 | 컴파일 타임 단언 |
핵심 원칙:
- 컴파일 타임에 알 수 있는 값은
constexpr로 선언해 런타임 비용을 제거합니다. - 반드시 컴파일 타임에만 실행돼야 하는 함수는
consteval을 사용합니다. if constexpr는 SFINAE보다 훨씬 가독성이 좋으므로 C++17 이상에서는 이를 우선 사용합니다.