콘텐츠로 이동

C++ 컴파일 타임 프로그래밍

개요 — 컴파일 타임 프로그래밍이란

섹션 제목: “개요 — 컴파일 타임 프로그래밍이란”

컴파일 타임 프로그래밍은 런타임이 아닌 컴파일 단계에서 계산을 수행하는 기법입니다. 결과가 컴파일 타임에 결정되므로 런타임 오버헤드가 없고, 값이 상수로 취급되어 최적화에 유리합니다.

C++ 컴파일 타임 도구:

  • constexpr — 컴파일 타임에 평가 가능한 함수·변수
  • consteval — 반드시 컴파일 타임에만 평가되는 함수 (C++20)
  • constinit — 컴파일 타임에 초기화됨을 보장 (C++20)
  • if constexpr — 컴파일 타임 조건 분기 (C++17)

1. constexpr — 컴파일 타임 평가 가능

섹션 제목: “1. constexpr — 컴파일 타임 평가 가능”
// 컴파일 타임 상수
constexpr int MaxSize = 256;
constexpr double Pi = 3.141592653589793;
// 배열 크기로 사용 가능 (런타임 변수는 불가)
int buffer[MaxSize]; // OK — MaxSize는 컴파일 타임 상수
// constexpr 함수 — 상수 인자 → 컴파일 타임 계산
// — 비상수 인자 → 런타임 계산 (일반 함수처럼)
constexpr int Factorial(int n)
{
return (n <= 1) ? 1 : n * Factorial(n - 1);
}
constexpr int Fact5 = Factorial(5); // 컴파일 타임: 120
int n = 5;
int FactN = Factorial(n); // 런타임: 120 (n이 런타임 값)
// 배열 크기, 템플릿 인자로 사용 가능
static_assert(Fact5 == 120);
int arr[Factorial(4)]; // int arr[24]

1.3 C++14 — constexpr 함수 제약 완화

섹션 제목: “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 — 컴파일 타임
// 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)

섹션 제목: “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)

섹션 제목: “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 비교

섹션 제목: “constexpr vs consteval vs constinit 비교”
키워드평가 시점수정 가능적용 대상
constexpr 변수컴파일 타임불가변수, 함수
constexpr 함수컴파일 또는 런타임함수
consteval 함수반드시 컴파일 타임함수
constinit 변수컴파일 타임 초기화가능정적 변수
const 변수컴파일 또는 런타임불가변수

4. if constexpr — 컴파일 타임 조건 분기 (C++17)

섹션 제목: “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"
// 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]";
}

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 — 컴파일 타임 단언

섹션 제목: “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. 실전 패턴 — 룩업 테이블 생성

섹션 제목: “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 constexprC++17템플릿 내 조건 분기
constevalC++20강제 컴파일 타임 함수
constinitC++20정적 변수 초기화 순서 보장
static_assertC++11컴파일 타임 단언

핵심 원칙:

  • 컴파일 타임에 알 수 있는 값은 constexpr로 선언해 런타임 비용을 제거합니다.
  • 반드시 컴파일 타임에만 실행돼야 하는 함수는 consteval을 사용합니다.
  • if constexpr는 SFINAE보다 훨씬 가독성이 좋으므로 C++17 이상에서는 이를 우선 사용합니다.

9. constexpr 함수의 함정과 주의사항

섹션 제목: “9. constexpr 함수의 함정과 주의사항”
// 함정 1: constexpr 함수가 항상 컴파일 타임에 평가되지는 않는다
constexpr int square(int x) { return x * x; }
int runtime_val = 5;
int r1 = square(runtime_val); // 런타임 평가 — constexpr이지만 런타임 인자
// 컴파일 타임 평가를 강제하려면 consteval 사용 또는:
constexpr int r2 = square(5); // 반드시 컴파일 타임
// 함정 2: constexpr 포인터 vs constexpr가 가리키는 값
constexpr int value = 42;
constexpr const int* ptr = &value; // 포인터 자체가 상수 (컴파일 타임 주소)
// const int* constexpr ptr2 = ...; // 문법 오류 — constexpr는 포인터 앞에
// 함정 3: std::string, std::vector는 constexpr 함수 내부에서만 사용 가능
constexpr auto make_string()
{
std::string s = "hello"; // C++20: OK (constexpr 문맥 내)
s += " world";
return s; // 주의: 반환값은 constexpr 변수에 저장 가능
}
// constexpr auto str = make_string(); // C++20에서는 가능
// 함정 4: constexpr 멤버 함수와 mutable
class Counter {
mutable int count_ = 0; // constexpr 컨텍스트에서 mutable 수정 불가
public:
constexpr int get() const { return count_; }
// constexpr void inc() const { ++count_; } // 오류: constexpr에서 mutable 수정 불가
};

10. 컴파일 타임 유한 상태 머신 (FSM)

섹션 제목: “10. 컴파일 타임 유한 상태 머신 (FSM)”
#include <array>
enum class State { Idle, Running, Paused, Stopped };
enum class Event { Start, Pause, Resume, Stop };
struct Transition {
State from;
Event event;
State to;
};
consteval auto make_fsm_table()
{
constexpr Transition transitions[] = {
{State::Idle, Event::Start, State::Running},
{State::Running, Event::Pause, State::Paused},
{State::Running, Event::Stop, State::Stopped},
{State::Paused, Event::Resume, State::Running},
{State::Paused, Event::Stop, State::Stopped},
};
return transitions; // 컴파일 타임 테이블
}
constexpr State transition(State current, Event event)
{
constexpr auto table = make_fsm_table();
for (const auto& t : table)
if (t.from == current && t.event == event)
return t.to;
return current; // 전이 없으면 현재 상태 유지
}
// 컴파일 타임 검증
static_assert(transition(State::Idle, Event::Start) == State::Running);
static_assert(transition(State::Running, Event::Pause) == State::Paused);
static_assert(transition(State::Paused, Event::Resume) == State::Running);

기능버전주요 용도
constexpr 변수C++11컴파일 타임 상수
constexpr 함수C++11/14/17/20컴파일 또는 런타임 계산
if constexprC++17템플릿 내 조건 분기
constevalC++20강제 컴파일 타임 함수
constinitC++20정적 변수 초기화 순서 보장
static_assertC++11컴파일 타임 단언

핵심 원칙:

  • 컴파일 타임에 알 수 있는 값은 constexpr로 선언해 런타임 비용을 제거합니다.
  • 반드시 컴파일 타임에만 실행돼야 하는 함수는 consteval을 사용합니다.
  • if constexpr는 SFINAE보다 훨씬 가독성이 좋으므로 C++17 이상에서는 이를 우선 사용합니다.
  • constexpr 함수에 런타임 인자를 넘기면 런타임 평가됩니다 — 강제하려면 consteval 또는 constexpr 변수에 결과를 저장합니다.