C++ 컴파일 타임 프로그래밍 | constexpr·consteval·if constexpr 완벽 가이드
이 글의 핵심
C++ 컴파일 타임 프로그래밍 완벽 가이드. constexpr·consteval·if constexpr로 컴파일 타임에 계산하고, 런타임 성능을 높입니다. 템플릿 메타프로그래밍과 비교하며 실무 활용법을 정리합니다.
들어가며
C++의 컴파일 타임 프로그래밍은 constexpr, consteval, if constexpr을 사용해 컴파일 타임에 계산하고, 런타임 성능을 높입니다.
비유로 말씀드리면, 런타임 계산은 주문이 들어올 때마다 요리하는 것, 컴파일 타임 계산은 미리 요리해 놓고 데우기만 하는 것에 가깝습니다. 미리 계산해 놓으면 실행 시간이 줄어듭니다.
이 글을 읽으면
- constexpr, consteval, if constexpr의 차이를 이해합니다
- 컴파일 타임 계산과 런타임 계산을 구분합니다
- 템플릿 메타프로그래밍과 비교합니다
- 실무 활용법과 주의사항을 파악합니다
목차
constexpr 기초
constexpr 변수
#include <iostream>
constexpr int MAX_SIZE = 100;
constexpr double PI = 3.14159;
int main() {
int arr[MAX_SIZE]; // 배열 크기로 사용 가능
std::cout << PI << std::endl;
return 0;
}
constexpr 함수
#include <iostream>
// 컴파일 타임 또는 런타임에 실행 가능
constexpr int square(int x) {
return x * x;
}
int main() {
constexpr int result = square(5); // 컴파일 타임에 계산
int arr[result]; // 배열 크기로 사용 가능
int x = 10;
int result2 = square(x); // 런타임에도 사용 가능
std::cout << result << ", " << result2 << std::endl;
return 0;
}
constexpr vs const
// const: 런타임 상수
const int x = 10;
// constexpr: 컴파일 타임 상수
constexpr int y = 10;
int arr1[x]; // ❌ 에러 (const는 배열 크기로 불가)
int arr2[y]; // ✅ OK (constexpr는 가능)
실전 구현
1) constexpr 함수
#include <iostream>
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int fact5 = factorial(5); // 120 (컴파일 타임)
std::cout << fact5 << std::endl;
return 0;
}
2) constexpr 클래스
#include <iostream>
class Point {
private:
int x_, y_;
public:
constexpr Point(int x, int y) : x_(x), y_(y) {}
constexpr int getX() const { return x_; }
constexpr int getY() const { return y_; }
constexpr int distanceSquared() const {
return x_ * x_ + y_ * y_;
}
};
int main() {
constexpr Point p(3, 4);
constexpr int dist = p.distanceSquared(); // 25 (컴파일 타임)
int arr[dist]; // OK
std::cout << dist << std::endl;
return 0;
}
3) if constexpr (C++17)
#include <iostream>
#include <type_traits>
template<typename T>
auto getValue(T t) {
if constexpr (std::is_pointer_v<T>) {
return *t; // 포인터면 역참조
} else {
return t; // 아니면 그대로
}
}
int main() {
int x = 10;
int* ptr = &x;
std::cout << getValue(x) << std::endl; // 10
std::cout << getValue(ptr) << std::endl; // 10
return 0;
}
4) consteval (C++20)
#include <iostream>
// 반드시 컴파일 타임에만 실행
consteval int sqr(int n) {
return n * n;
}
int main() {
constexpr int x = sqr(5); // ✅ OK
int y = 10;
// int z = sqr(y); // ❌ 에러: 런타임 값
std::cout << x << std::endl;
return 0;
}
고급 활용
1) 컴파일 타임 문자열 해시
#include <iostream>
constexpr unsigned int hash(const char* str) {
unsigned int hash = 5381;
while (*str) {
hash = ((hash << 5) + hash) + (*str++);
}
return hash;
}
int main() {
constexpr unsigned int startHash = hash("start");
constexpr unsigned int stopHash = hash("stop");
const char* command = "start";
switch (hash(command)) {
case startHash:
std::cout << "시작" << std::endl;
break;
case stopHash:
std::cout << "정지" << std::endl;
break;
default:
std::cout << "알 수 없음" << std::endl;
}
return 0;
}
2) 컴파일 타임 배열 생성
#include <array>
#include <iostream>
template<size_t N>
constexpr auto generateFibonacci() {
std::array<int, N> result{};
if (N > 0) result[0] = 0;
if (N > 1) result[1] = 1;
for (size_t i = 2; i < N; ++i) {
result[i] = result[i - 1] + result[i - 2];
}
return result;
}
int main() {
constexpr auto fib = generateFibonacci<10>();
for (int x : fib) {
std::cout << x << " "; // 0 1 1 2 3 5 8 13 21 34
}
std::cout << std::endl;
return 0;
}
3) 템플릿 메타프로그래밍
#include <iostream>
// 재귀 템플릿
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N - 1>::value;
};
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
int main() {
std::cout << Factorial<5>::value << std::endl; // 120 (컴파일 타임)
return 0;
}
성능 비교
벤치마크: 피보나치
#include <chrono>
#include <iostream>
// 런타임
int runtimeFib(int n) {
if (n <= 1) return n;
return runtimeFib(n - 1) + runtimeFib(n - 2);
}
// 컴파일 타임
constexpr int compiletimeFib(int n) {
if (n <= 1) return n;
return compiletimeFib(n - 1) + compiletimeFib(n - 2);
}
int main() {
// 런타임 계산 (느림)
auto start = std::chrono::high_resolution_clock::now();
int r = runtimeFib(40);
auto end = std::chrono::high_resolution_clock::now();
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
std::cout << "런타임: " << time << "ms" << std::endl;
// 컴파일 타임 계산 (즉시)
constexpr int c = compiletimeFib(40);
std::cout << "컴파일 타임: 0ms (이미 계산됨)" << std::endl;
return 0;
}
결과:
| 방법 | 시간 |
|---|---|
| 런타임 | 1200ms |
| 컴파일 타임 | 0ms |
결론: 컴파일 타임 계산이 압도적으로 빠름
실무 사례
사례 1: 룩업 테이블 생성
#include <array>
#include <iostream>
constexpr auto generateSinTable() {
constexpr size_t SIZE = 360;
std::array<double, SIZE> table{};
constexpr double PI = 3.14159265358979323846;
for (size_t i = 0; i < SIZE; ++i) {
double radians = i * PI / 180.0;
table[i] = radians; // 실제로는 sin 계산
}
return table;
}
int main() {
constexpr auto sinTable = generateSinTable();
std::cout << sinTable[45] << std::endl; // 0.785398 (45도)
return 0;
}
사례 2: 타입 체크
#include <iostream>
#include <type_traits>
template<typename T>
constexpr bool isNumeric() {
if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>) {
return true;
} else {
return false;
}
}
template<typename T>
void process(T value) {
if constexpr (isNumeric<T>()) {
std::cout << "숫자: " << value << std::endl;
} else {
std::cout << "문자열: " << value << std::endl;
}
}
int main() {
process(42); // 숫자: 42
process("hello"); // 문자열: hello
return 0;
}
사례 3: 컴파일 타임 정렬
#include <algorithm>
#include <array>
#include <iostream>
constexpr void bubbleSort(int* arr, int n) {
for (int i = 0; i < n - 1; ++i) {
for (int j = 0; j < n - i - 1; ++j) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
constexpr auto getSortedArray() {
std::array<int, 5> arr = {5, 2, 8, 1, 9};
bubbleSort(arr.data(), arr.size());
return arr;
}
int main() {
constexpr auto sorted = getSortedArray();
for (int x : sorted) {
std::cout << x << " "; // 1 2 5 8 9
}
std::cout << std::endl;
return 0;
}
사례 4: 비트 연산 최적화
#include <iostream>
constexpr unsigned int reverseBits(unsigned int n) {
unsigned int result = 0;
for (int i = 0; i < 32; ++i) {
result <<= 1;
result |= (n & 1);
n >>= 1;
}
return result;
}
int main() {
constexpr unsigned int reversed = reverseBits(0b10110000);
std::cout << std::hex << reversed << std::endl;
return 0;
}
트러블슈팅
문제 1: constexpr 제약
증상: 컴파일 에러
// ❌ static 변수 불가
constexpr int bad() {
static int x = 0; // 에러
return x++;
}
// ❌ 동적 할당 불가 (C++17)
constexpr int bad2() {
int* p = new int(10); // 에러
return *p;
}
// ✅ OK
constexpr int good(int x) {
return x * 2;
}
문제 2: 런타임 값 사용
증상: 컴파일 에러
#include <iostream>
int main() {
int x;
std::cin >> x;
// ❌ 에러: 런타임 값
constexpr int y = x * 2;
// ✅ OK: 런타임 변수
int y2 = x * 2;
return 0;
}
문제 3: 복잡한 계산
증상: 컴파일 시간 증가
// ❌ 컴파일 시간 증가
constexpr int slowFib(int n) {
if (n <= 1) return n;
return slowFib(n - 1) + slowFib(n - 2);
}
constexpr int x = slowFib(50); // 컴파일 매우 느림!
// ✅ 메모이제이션
constexpr auto fastFib() {
std::array<int, 50> result{};
result[0] = 0;
result[1] = 1;
for (int i = 2; i < 50; ++i) {
result[i] = result[i - 1] + result[i - 2];
}
return result;
}
constexpr auto fibTable = fastFib();
constexpr int y = fibTable[49]; // 빠름
문제 4: constexpr vs consteval 혼동
증상: 의도와 다른 동작
// constexpr: 컴파일 타임 또는 런타임
constexpr int square(int n) {
return n * n;
}
int x = 10;
int y = square(x); // ✅ 런타임에 실행
// consteval: 컴파일 타임만
consteval int sqr(int n) {
return n * n;
}
constexpr int z = sqr(5); // ✅ OK
// int w = sqr(x); // ❌ 에러: 런타임 값
마무리
컴파일 타임 프로그래밍은 constexpr, consteval, if constexpr을 사용해 런타임 성능을 높입니다.
핵심 요약
-
constexpr
- 컴파일 타임 또는 런타임에 실행
- 변수, 함수, 클래스에 사용
- 배열 크기, 템플릿 인자로 사용 가능
-
consteval (C++20)
- 컴파일 타임에만 실행
- 즉시 함수
- 런타임 값 사용 불가
-
if constexpr (C++17)
- 컴파일 타임 분기
- 타입에 따른 조건부 컴파일
- 템플릿 특수화 대체
-
성능
- 컴파일 타임 계산: 런타임 오버헤드 없음
- 컴파일 시간 증가 가능
- 복잡한 계산은 메모이제이션
선택 가이드
| 상황 | 권장 | 이유 |
|---|---|---|
| 컴파일 타임 상수 | constexpr | 배열 크기 등 |
| 컴파일 타임 전용 | consteval | 의도 명확 |
| 타입별 분기 | if constexpr | 조건부 컴파일 |
| 런타임도 필요 | constexpr | 유연성 |
코드 예제 치트시트
// constexpr 변수
constexpr int MAX_SIZE = 100;
// constexpr 함수
constexpr int square(int x) {
return x * x;
}
// constexpr 클래스
class Point {
public:
constexpr Point(int x, int y) : x_(x), y_(y) {}
constexpr int getX() const { return x_; }
private:
int x_, y_;
};
// if constexpr
template<typename T>
auto getValue(T t) {
if constexpr (std::is_pointer_v<T>) {
return *t;
} else {
return t;
}
}
// consteval
consteval int sqr(int n) {
return n * n;
}
다음 단계
- constexpr 함수: C++ constexpr 함수
- constexpr if: C++ constexpr if
- constexpr 기초: C++ constexpr 기초
참고 자료
- “C++20 The Complete Guide” - Nicolai M. Josuttis
- “Effective Modern C++” - Scott Meyers
- cppreference: https://en.cppreference.com/w/cpp/language/constexpr
한 줄 정리: constexpr로 컴파일 타임에 계산하고, consteval로 컴파일 타임 전용 함수를 만들며, if constexpr로 타입별 조건부 컴파일을 수행한다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ constexpr 함수 | “컴파일 타임 함수” 가이드
- C++ constexpr if | “컴파일 타임 분기” 가이드
- C++ constexpr 함수와 변수 | 컴파일 타임에 계산하기 [#26-1]
관련 글
- C++ 컴파일 타임 최적화 | constexpr·PCH·모듈·ccache·Unity 빌드 [#15-3]