Skip to content

constexpr·consteval·if constexpr — 컴파일 타임 프로그래밍 완전 가이드

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

Section titled “개요 — 컴파일 타임 프로그래밍이란”

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

C++ 컴파일 타임 도구:

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

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

Section titled “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 함수 제약 완화

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

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

핵심 원칙:

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