C++ constexpr 고급 가이드 | constexpr 컨테이너·알고리즘·문자열·new/delete 실전
이 글의 핵심
C++20 constexpr std::vector·std::string·알고리즘, constexpr new/delete, 컴파일 타임 컨테이너·알고리즘 완전 예제. 문제 시나리오, 자주 발생하는 에러, 베스트 프랙티스, 프로덕션 패턴.
들어가며: “컴파일 타임에 컨테이너와 알고리즘을 돌리고 싶어요”
구체적인 문제 시나리오
constexpr 기초를 익힌 뒤, 이런 고급 요구가 생깁니다:
- 설정값 목록을 컴파일 타임에 파싱해 정렬·검증하고 싶다
- 프로토콜 명령어 테이블을 문자열 리터럴에서 생성해
std::vector처럼 다루고 싶다 - CRC/해시 테이블을
std::array가 아닌 동적 크기로 만들고 싶다 - JSON 스키마의 필드 목록을 컴파일 타임에 정렬해 이진 검색용으로 쓰고 싶다
- 단위 테스트에서 고정된
std::vector<int>데이터를 컴파일 타임에 만들어 재현성을 보장하고 싶다
추가 문제 시나리오
시나리오 1: 컴파일 타임 설정 파싱
"1024,2048,4096" 같은 문자열을 컴파일 타임에 파싱해 std::vector<size_t>로 만들고, 정렬·중복 제거 후 static_assert로 검증하고 싶습니다. C++20 constexpr std::string, std::vector가 없으면 불가능했습니다.
시나리오 2: 프로토콜 명령어 디스패치 테이블
"START", "STOP", "PAUSE" 등 문자열 리터럴을 컴파일 타임에 해시해 std::array에 넣고, 런타임에는 해시만 계산해 O(1) 테이블 조회로 명령을 처리하고 싶습니다. constexpr 알고리즘으로 테이블을 정렬해 이진 검색도 가능합니다.
시나리오 3: 임베디드 메모리 맵 검증
레지스터 오프셋 배열이 오름차순인지, 겹치는 영역이 없는지 컴파일 타임에 검증하고 싶습니다. constexpr std::sort와 반복문으로 검증 로직을 짜면, 잘못된 맵은 컴파일 에러로 잡을 수 있습니다.
시나리오 4: 테스트 픽스처 생성
단위 테스트에서 {1, 2, 3, 5, 8, 13} 같은 피보나치 시퀀스를 컴파일 타임에 생성해, 런타임 초기화 비용 없이 테스트를 돌리고 싶습니다.
시나리오 5: 컴파일 타임 Base64 디코딩
"SGVsbG8=" 같은 Base64 문자열을 컴파일 타임에 디코딩해 std::string 또는 std::vector<uint8_t>로 만들고, 런타임에는 그 결과만 사용하고 싶습니다.
C++20에서는 constexpr std::vector, constexpr std::string, constexpr 알고리즘, constexpr new/delete가 표준에 들어와, 위 시나리오들을 모두 구현할 수 있습니다.
C++20 constexpr 확장 개요
flowchart TD
A[C++20 constexpr 확장] --> B["std vector"]
A --> C["std string"]
A --> D[알고리즘: sort, find, copy...]
A --> E[new/delete]
B --> F[컴파일 타임 동적 컨테이너]
C --> G[컴파일 타임 문자열 조작]
D --> H[컴파일 타임 정렬·검색]
E --> I[컴파일 타임 동적 할당]
이 글을 읽으면
- constexpr
std::vector,std::string,std::array를 컴파일 타임에 다룰 수 있습니다. - constexpr
std::sort,std::find등 알고리즘을 컴파일 타임에 사용할 수 있습니다. - constexpr
new/delete의 동작과 제약을 이해할 수 있습니다. - 자주 발생하는 에러와 해결법을 알 수 있습니다.
- 프로덕션에서 바로 쓸 수 있는 고급 패턴을 적용할 수 있습니다.
개념을 잡는 비유
템플릿 인자 자리는 붕어빵 틀의 칸 수가 정해지듯, 컴파일 시점에 크기·상수가 박혀 있어야 하는 경우가 많습니다. constexpr·컴파일 타임 계산은 그 값을 미리 찍어내어, 배열 크기와 static_assert 같은 곳에 그대로 얹을 수 있게 해 줍니다.
목차
- 문제 시나리오 상세
- constexpr 컨테이너
- constexpr 알고리즘
- constexpr 문자열
- constexpr new/delete (C++20)
- 자주 발생하는 에러와 해결법
- 베스트 프랙티스
- 프로덕션 패턴
- 성능 및 컴파일 타임 비용
1. 문제 시나리오 상세
시나리오 A: 컴파일 타임 설정 검증
문제: 버퍼 크기 목록 "64,128,256,512"를 파싱해, 모두 2의 거듭제곱인지, 오름차순인지 컴파일 타임에 검증하고 싶습니다.
C++17 이하: std::vector가 constexpr가 아니어서, 파싱 결과를 컴파일 타임에 담을 수 없었습니다. std::array로 고정 크기를 쓰거나, 런타임 검증만 가능했습니다.
C++20 해법: constexpr std::vector로 파싱 → 정렬 → 검증을 모두 컴파일 타임에 수행합니다.
// C++20: g++ -std=c++20 -o parse parse.cpp
#include <algorithm>
#include <cstddef>
#include <string>
#include <vector>
constexpr std::vector<std::size_t> parse_sizes(const char* s) {
std::vector<std::size_t> result;
std::size_t val = 0;
while (*s) {
if (*s >= '0' && *s <= '9') {
val = val * 10 + (*s - '0');
} else if (*s == ',') {
result.push_back(val);
val = 0;
}
++s;
}
if (val != 0) result.push_back(val);
return result;
}
constexpr bool is_power_of_two(std::size_t n) {
return n > 0 && (n & (n - 1)) == 0;
}
constexpr bool validate_sizes(const std::vector<std::size_t>& v) {
auto sorted = v;
std::sort(sorted.begin(), sorted.end());
for (std::size_t i = 0; i < sorted.size(); ++i) {
if (!is_power_of_two(sorted[i])) return false;
if (i > 0 && sorted[i] <= sorted[i - 1]) return false;
}
return true;
}
int main() {
constexpr auto sizes = parse_sizes("64,128,256,512");
static_assert(validate_sizes(sizes));
return 0;
}
시나리오 B: 프로토콜 명령어 해시 테이블
문제: "START", "STOP", "PAUSE", "RESUME"을 컴파일 타임에 해시해, 런타임에는 해시만 계산해 O(1) 조회로 명령을 처리하고 싶습니다.
// C++20
#include <array>
#include <cstddef>
#include <string_view>
constexpr std::size_t hash_fnv1a(const char* str) {
std::size_t hash = 14695981039346656037ULL;
while (*str) {
hash ^= static_cast<unsigned char>(*str++);
hash *= 1099511628211ULL;
}
return hash;
}
template <std::size_t N>
constexpr auto make_command_table(const char* const (&commands)[N]) {
std::array<std::pair<std::size_t, std::size_t>, N> table;
for (std::size_t i = 0; i < N; ++i) {
table[i] = {hash_fnv1a(commands[i]), i};
}
std::sort(table.begin(), table.end());
return table;
}
constexpr const char* COMMANDS[] = {"START", "STOP", "PAUSE", "RESUME"};
constexpr auto CMD_TABLE = make_command_table(COMMANDS);
int dispatch(std::string_view cmd) {
auto h = hash_fnv1a(cmd.data());
auto it = std::lower_bound(CMD_TABLE.begin(), CMD_TABLE.end(), h,
{ return p.first < key; });
if (it != CMD_TABLE.end() && it->first == h)
return static_cast<int>(it->second);
return -1;
}
시나리오 C: 컴파일 타임 피보나치 시퀀스
문제: 테스트 픽스처로 {1, 1, 2, 3, 5, 8, 13, 21}을 컴파일 타임에 생성하고 싶습니다.
// C++20
#include <vector>
constexpr std::vector<int> fib_sequence(int n) {
std::vector<int> result;
if (n >= 1) result.push_back(1);
if (n >= 2) result.push_back(1);
for (int i = 2; i < n; ++i) {
result.push_back(result[i - 1] + result[i - 2]);
}
return result;
}
// 컴파일 타임에 8개 피보나치 수 생성
constexpr auto FIB8 = fib_sequence(8);
// FIB8 = {1, 1, 2, 3, 5, 8, 13, 21}
2. constexpr 컨테이너
2.1 constexpr std::array (C++17)
std::array는 C++17부터 constexpr 생성자와 멤버 함수가 있어, 컴파일 타임에 완전히 사용할 수 있습니다.
#include <array>
#include <algorithm>
// 컴파일 타임에 배열 생성 및 정렬
constexpr std::array<int, 5> make_sorted() {
std::array<int, 5> a = {5, 2, 8, 1, 9};
std::sort(a.begin(), a.end());
return a;
}
constexpr auto SORTED = make_sorted();
static_assert(SORTED[0] == 1);
static_assert(SORTED[4] == 9);
2.2 constexpr std::vector (C++20)
C++20에서 std::vector는 constexpr 생성자, push_back, resize, 소멸자 등이 constexpr로 확장되었습니다.
// C++20
#include <vector>
constexpr std::vector<int> make_vector() {
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
return v;
}
constexpr auto V = make_vector();
static_assert(V.size() == 3);
static_assert(V[0] == 1 && V[1] == 2 && V[2] == 3);
주의: constexpr 평가 중 할당된 메모리는 평가 종료 시 자동 해제됩니다. constexpr auto V = make_vector();에서 V는 평가 결과로 값이 복사되며, std::vector의 내부 동적 메모리는 컴파일 타임 평가용으로만 쓰입니다. 최종 V는 컴파일러가 상수로 초기화한 std::vector입니다.
2.3 constexpr std::vector 고급 예제: 동적 크기 테이블
// C++20: 컴파일 타임에 N개 항목 테이블 생성
template <typename Func>
constexpr auto make_table(size_t n, Func f) {
std::vector<decltype(f(0))> result;
result.reserve(n);
for (size_t i = 0; i < n; ++i) {
result.push_back(f(i));
}
return result;
}
constexpr int square(int x) { return x * x; }
constexpr auto SQUARES = make_table(10, square);
// SQUARES = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
static_assert(SQUARES.size() == 10);
static_assert(SQUARES[5] == 25);
2.4 constexpr 컨테이너 제약 사항
| 항목 | std::array | std::vector (C++20) |
|---|---|---|
| constexpr 생성 | ✅ | ✅ |
| push_back / resize | N/A (고정 크기) | ✅ |
| 반복자, operator[] | ✅ | ✅ |
| allocator | 기본만 | 기본만 (커스텀 allocator는 제한) |
| 예외 | constexpr 평가 중 throw 불가 | 동일 |
3. constexpr 알고리즘
3.1 C++20 constexpr 알고리즘 목록
C++20에서 다음 알고리즘들이 constexpr로 확장되었습니다:
std::sort,std::stable_sortstd::find,std::find_if,std::find_if_notstd::copy,std::copy_if,std::fillstd::min_element,std::max_elementstd::lower_bound,std::upper_bound,std::binary_searchstd::reverse,std::rotatestd::all_of,std::any_of,std::none_of- 기타 다수
3.2 constexpr 정렬 예제
// C++20
#include <algorithm>
#include <array>
constexpr std::array<int, 6> sort_at_compile_time() {
std::array<int, 6> a = {6, 2, 9, 1, 5, 3};
std::sort(a.begin(), a.end());
return a;
}
constexpr auto SORTED_ARR = sort_at_compile_time();
static_assert(SORTED_ARR[0] == 1);
static_assert(SORTED_ARR[5] == 9);
3.3 constexpr 이진 검색
// C++20: 컴파일 타임에 정렬된 배열에서 이진 검색
#include <algorithm>
#include <array>
constexpr std::array<int, 5> SORTED = {1, 3, 5, 7, 9};
constexpr bool contains(int key) {
return std::binary_search(SORTED.begin(), SORTED.end(), key);
}
static_assert(contains(5));
static_assert(!contains(4));
3.4 constexpr std::vector + std::sort
// C++20
#include <algorithm>
#include <vector>
constexpr std::vector<int> sorted_vector() {
std::vector<int> v = {30, 10, 50, 20, 40};
std::sort(v.begin(), v.end());
return v;
}
constexpr auto SV = sorted_vector();
static_assert(SV[0] == 10);
static_assert(SV[4] == 50);
3.5 constexpr all_of / any_of
// C++20
#include <algorithm>
#include <array>
constexpr std::array<int, 4> EVENS = {2, 4, 6, 8};
constexpr bool all_even() {
return std::all_of(EVENS.begin(), EVENS.end(),
{ return x % 2 == 0; });
}
static_assert(all_even());
4. constexpr 문자열
4.1 constexpr std::string (C++20)
C++20에서 std::string의 생성자, operator+=, push_back, substr, find 등이 constexpr로 확장되었습니다.
// C++20
#include <string>
constexpr std::string make_greeting(const char* name) {
std::string result = "Hello, ";
result += name;
result += "!";
return result;
}
constexpr auto GREETING = make_greeting("World");
static_assert(GREETING == "Hello, World!");
static_assert(GREETING.size() == 12);
4.2 constexpr 문자열 파싱
// C++20: "key=value" 형식 파싱
#include <string>
#include <utility>
constexpr std::pair<std::string, std::string> parse_key_value(const char* s) {
std::string key, value;
while (*s && *s != '=') {
key += *s++;
}
if (*s == '=') ++s;
while (*s) {
value += *s++;
}
return {key, value};
}
constexpr auto KV = parse_key_value("size=4096");
static_assert(KV.first == "size");
static_assert(KV.second == "4096");
4.3 constexpr 문자열 검색 및 조작
// C++20
#include <string>
constexpr std::string replace_first(const std::string& s,
const std::string& from, const std::string& to) {
std::string result = s;
auto pos = result.find(from);
if (pos != std::string::npos) {
result.replace(pos, from.size(), to);
}
return result;
}
constexpr auto R = replace_first("foo_bar_baz", "_", "-");
static_assert(R == "foo-bar_baz");
4.4 constexpr std::string_view
std::string_view는 C++17부터 constexpr 생성자가 있으며, C++20에서 substr, find 등이 constexpr로 확장되었습니다.
// C++20
#include <string_view>
constexpr std::string_view trim_leading(std::string_view sv) {
while (!sv.empty() && sv[0] == ' ') {
sv.remove_prefix(1);
}
return sv;
}
constexpr auto TRIMMED = trim_leading(" hello");
static_assert(TRIMMED == "hello");
5. constexpr new/delete (C++20)
5.1 기본 개념
C++20에서 constexpr 평가 중 new와 delete가 허용됩니다. 할당된 메모리는 평가가 끝나면 자동으로 해제되므로, 런타임 메모리 누수와 무관합니다.
// C++20
constexpr int* dynamic_sum(int a, int b) {
int* p = new int(a + b);
int result = *p;
delete p;
return new int(result); // 주의: 이 포인터는 평가 종료 후 유효하지 않음
}
// 실제로는 값만 필요하므로 이렇게 쓰는 것이 좋음
constexpr int dynamic_sum_safe(int a, int b) {
int* p = new int(a + b);
int result = *p;
delete p;
return result;
}
constexpr int x = dynamic_sum_safe(3, 5);
static_assert(x == 8);
5.2 constexpr new/delete 규칙
- 평가 종료 시 모든 할당이 해제되어야 함: 평가가 끝날 때
new로 할당된 메모리가 남아 있으면 ill-formed입니다. - placement new는 제한적: 일반
new/delete만 constexpr 평가에 사용할 수 있습니다. - 예외: constexpr 평가 중
new가 실패하면 예외를 던질 수 있지만, constexpr 맥락에서는 예외가 허용되지 않는 경우가 많아 실질적으로 실패 시 컴파일 에러가 됩니다.
5.3 constexpr 동적 배열 예제
// C++20: 컴파일 타임에 동적 크기 배열 생성
constexpr int* make_dynamic_array(size_t n) {
int* p = new int[n];
for (size_t i = 0; i < n; ++i) {
p[i] = static_cast<int>(i * i);
}
return p;
}
// 주의: make_dynamic_array가 반환하는 포인터는 평가 종료 후 유효하지 않음
// 따라서 값만 추출해 사용
constexpr int get_square_at(size_t n) {
int* p = make_dynamic_array(n + 1);
int result = p[n];
delete[] p;
return result;
}
static_assert(get_square_at(5) == 25);
5.4 constexpr new를 활용한 벡터 스타일 구현
// C++20: constexpr 평가 중 new/delete로 동적 배열 시뮬레이션
constexpr size_t count_digits(unsigned long long n) {
if (n == 0) return 1;
size_t count = 0;
while (n) {
++count;
n /= 10;
}
return count;
}
constexpr auto DIGIT_COUNT = count_digits(12345);
static_assert(DIGIT_COUNT == 5);
5.5 constexpr new/delete 제약 요약
| 항목 | 허용 |
|---|---|
new T | ✅ |
new T[n] | ✅ |
delete p | ✅ |
delete[] p | ✅ |
| placement new | ❌ (일반적으로) |
| 커스텀 allocator | 제한적 |
| 평가 종료 시 미해제 메모리 | ❌ ill-formed |
6. 자주 발생하는 에러와 해결법
에러 1: C++17에서 std::vector/std::string을 constexpr로 사용
원인: std::vector와 std::string의 constexpr 지원은 C++20부터입니다.
// ❌ C++17에서 컴파일 에러
// constexpr std::vector<int> v = {1, 2, 3};
// ✅ C++20 사용
// g++ -std=c++20 ...
constexpr std::vector<int> v = {1, 2, 3};
해결: -std=c++20 이상을 사용하고, C++17이 필수라면 std::array로 대체합니다.
에러 2: constexpr 평가 중 throw
원인: constexpr 평가 맥락에서는 예외를 던지면 안 됩니다.
// ❌ 컴파일 에러
// constexpr int bad(int x) {
// if (x < 0) throw std::invalid_argument("negative");
// return x;
// }
// ✅ 조건 검사 후 에러 시 컴파일 실패 유도
constexpr int good(int x) {
if (x < 0) {
// constexpr에서 throw 불가 → static_assert로 대체
return -1; // 또는 조건을 static_assert로 검증
}
return x;
}
template <int X>
void use() {
static_assert(X >= 0, "X must be non-negative");
}
에러 3: constexpr new 후 delete 누락
원인: constexpr 평가가 끝날 때 new로 할당된 메모리가 남아 있으면 ill-formed입니다.
// ❌ 컴파일 에러: 메모리 누수
// constexpr int* leak() {
// return new int(42);
// }
// ✅ 반드시 delete
constexpr int safe() {
int* p = new int(42);
int r = *p;
delete p;
return r;
}
에러 4: constexpr 함수에서 I/O 또는 전역 상태
원인: constexpr 평가 중에는 std::cout, 파일 입출력, 전역 변수 수정이 불가능합니다.
// ❌ 컴파일 에러
// constexpr int bad() {
// std::cout << "hello";
// return 42;
// }
// ✅ 순수 계산만
constexpr int good(int x) {
return x * 2;
}
에러 5: constexpr std::string에 런타임 포인터 전달
원인: consteval이 아닌 constexpr 함수에 런타임 문자열을 넘기면, 해당 호출은 런타임에 실행됩니다. 상수 식이 필요한 곳에 그 결과를 넣으면 에러가 납니다.
constexpr std::string concat(const char* a, const char* b) {
std::string s = a;
s += b;
return s;
}
int main() {
const char* rt = "runtime"; // 런타임 포인터
// static_assert(concat(rt, "x").size() > 0); // ❌ 에러
auto s = concat("hello", "world"); // ✅ 런타임 호출 OK
constexpr auto c = concat("a", "b"); // ✅ 컴파일 타임
static_assert(c == "ab");
return 0;
}
에러 6: 컴파일러별 constexpr 한계
원인: GCC, Clang, MSVC마다 constexpr 평가 단계 수, 재귀 깊이, std::vector/std::string 지원 정도가 다릅니다.
| 컴파일러 | constexpr 단계 기본값 | constexpr vector/string |
|---|---|---|
| GCC 10+ | 512 | ✅ |
| Clang 10+ | 512 | ✅ |
| MSVC 2019 16.8+ | 제한적 | ✅ (일부) |
해결: -fconstexpr-ops-limit, -fconstexpr-depth 등으로 조정할 수 있으나, 복잡한 constexpr는 단순화하는 것이 이식성에 좋습니다.
에러 7: constexpr 알고리즘에 비교 불가 타입
원인: std::sort 등은 operator< 또는 비교 함수가 필요합니다. constexpr 평가 중에는 해당 연산이 constexpr 가능해야 합니다.
// ✅ int 등 기본 타입은 문제없음
constexpr std::array<int, 3> a = {3, 1, 2};
std::sort(a.begin(), a.end()); // OK
// 사용자 정의 타입은 constexpr operator< 필요
struct Point {
int x, y;
constexpr bool operator<(const Point& o) const {
return x < o.x || (x == o.x && y < o.y);
}
};
에러 8: constexpr vector의 allocator
원인: constexpr std::vector는 기본 std::allocator만 지원합니다. 커스텀 allocator를 쓰면 constexpr가 아닐 수 있습니다.
// ❌ 커스텀 allocator는 constexpr 제한
// std::vector<int, MyAllocator<int>> v;
// ✅ 기본 allocator 사용
constexpr std::vector<int> v = {1, 2, 3};
7. 베스트 프랙티스
1. C++20 이상에서 constexpr 컨테이너 활용
std::vector, std::string을 컴파일 타임에 쓰려면 반드시 C++20을 사용합니다. C++17 이하에서는 std::array와 고정 크기 버퍼로 대체합니다.
2. constexpr new는 꼭 필요한 경우만
constexpr new/delete는 복잡도를 높이고 컴파일러 부담을 늘립니다. std::vector로 대체 가능하면 std::vector를 우선합니다.
// ✅ 권장: std::vector
constexpr std::vector<int> make_data() {
std::vector<int> v;
for (int i = 0; i < 10; ++i) v.push_back(i * i);
return v;
}
// ⚠️ new/delete는 꼭 필요할 때만
constexpr int with_new() {
int* p = new int(42);
int r = *p;
delete p;
return r;
}
3. 컴파일 타임 검증은 static_assert와 함께
constexpr로 계산한 결과는 static_assert로 검증해, 잘못된 설정이면 컴파일 단계에서 막습니다.
constexpr int parse_buffer_size(const char* s) {
int r = 0;
while (*s >= '0' && *s <= '9') {
r = r * 10 + (*s - '0');
++s;
}
return r;
}
constexpr int BUF = parse_buffer_size("4096");
static_assert(BUF >= 64 && BUF <= 65536, "buffer size out of range");
4. 반복문 선호, 깊은 재귀 지양
constexpr 재귀가 깊어지면 컴파일러 한계에 걸릴 수 있습니다. 반복문으로 전환하면 이식성이 좋습니다.
// ✅ 권장: 반복문
constexpr int sum_loop(int n) {
int s = 0;
for (int i = 0; i <= n; ++i) s += i;
return s;
}
// ⚠️ 깊은 재귀는 컴파일러 한계
// constexpr int sum_recursive(int n) {
// return n <= 0 ? 0 : n + sum_recursive(n - 1);
// }
5. constexpr 결과는 constexpr 변수에 저장
컴파일 타임에 계산된 값을 여러 곳에서 쓰려면 constexpr 변수로 저장해 재사용합니다.
constexpr auto TABLE = make_crc32_table();
// 이후 TABLE을 여러 함수에서 사용
6. 컴파일 타임 전용이면 consteval
“반드시 컴파일 타임에만” 평가되어야 하는 함수는 consteval로 선언해, 실수로 런타임 호출하는 것을 막습니다.
consteval int must_be_compile_time(int x) {
return x * 2;
}
8. 프로덕션 패턴
패턴 1: 컴파일 타임 설정 파싱 + 검증
// C++20: "key1=val1,key2=val2" 파싱 후 검증
#include <string>
#include <vector>
constexpr std::vector<std::pair<std::string, std::string>>
parse_config(const char* s) {
std::vector<std::pair<std::string, std::string>> result;
std::string key, value;
enum { Key, Value } state = Key;
while (*s) {
if (*s == '=') {
state = Value;
++s;
continue;
}
if (*s == ',') {
result.push_back({key, value});
key.clear();
value.clear();
state = Key;
++s;
continue;
}
if (state == Key) key += *s;
else value += *s;
++s;
}
if (!key.empty()) result.push_back({key, value});
return result;
}
constexpr auto CONFIG = parse_config("size=4096,timeout=30");
static_assert(CONFIG.size() == 2);
패턴 2: 컴파일 타임 정렬된 명령어 테이블
// C++20: 명령어 문자열 → 인덱스 매핑 (정렬 후 이진 검색)
#include <algorithm>
#include <array>
#include <string_view>
template <size_t N>
constexpr auto make_sorted_command_table(const char* const (&cmds)[N]) {
std::array<std::pair<std::string_view, size_t>, N> table;
for (size_t i = 0; i < N; ++i) {
table[i] = {cmds[i], i};
}
std::sort(table.begin(), table.end(),
{ return a.first < b.first; });
return table;
}
constexpr const char* COMMANDS[] = {"PAUSE", "RESUME", "START", "STOP"};
constexpr auto CMD_TABLE = make_sorted_command_table(COMMANDS);
패턴 3: 컴파일 타임 Base64 디코딩 (간략)
// C++20: Base64 디코딩 (개념 예시)
#include <cstdint>
#include <string>
#include <vector>
constexpr int b64_index(char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
if (c == '+') return 62;
if (c == '/') return 63;
return -1;
}
constexpr std::vector<uint8_t> b64_decode(std::string_view sv) {
std::vector<uint8_t> out;
int val = 0, bits = 0;
for (char c : sv) {
if (c == '=') break;
int idx = b64_index(c);
if (idx < 0) continue;
val = (val << 6) | idx;
bits += 6;
if (bits >= 8) {
bits -= 8;
out.push_back(static_cast<uint8_t>((val >> bits) & 0xFF));
val &= (1 << bits) - 1;
}
}
return out;
}
constexpr auto DECODED = b64_decode("SGVsbG8=");
static_assert(DECODED.size() == 5);
static_assert(DECODED[0] == 'H' && DECODED[4] == 'o');
패턴 4: 타입별 constexpr 상수
template <typename T>
struct type_constants;
template <>
struct type_constants<int> {
static constexpr size_t max_digits = 10;
static constexpr int min_val = -2147483648;
static constexpr int max_val = 2147483647;
};
template <>
struct type_constants<long long> {
static constexpr size_t max_digits = 19;
};
template <typename T>
constexpr size_t buffer_size_for = type_constants<T>::max_digits + 2;
패턴 5: constexpr 레지스터 맵 검증
// C++20: 레지스터 오프셋 배열이 오름차순인지 검증
#include <algorithm>
#include <array>
constexpr std::array<uint32_t, 4> REG_OFFSETS = {0x00, 0x04, 0x08, 0x0C};
constexpr bool validate_reg_map() {
auto sorted = REG_OFFSETS;
std::sort(sorted.begin(), sorted.end());
for (size_t i = 0; i < sorted.size(); ++i) {
if (sorted[i] != REG_OFFSETS[i]) return false;
if (i > 0 && sorted[i] - sorted[i-1] != 4) return false;
}
return true;
}
static_assert(validate_reg_map());
패턴 6: 컴파일 타임 테스트 픽스처
// C++20: 테스트용 고정 데이터
constexpr std::vector<int> make_test_fixture() {
std::vector<int> v;
v.reserve(10);
for (int i = 0; i < 10; ++i) {
v.push_back(i * 11);
}
return v;
}
constexpr auto TEST_DATA = make_test_fixture();
// 단위 테스트에서 TEST_DATA 사용, 런타임 초기화 비용 없음
9. 성능 및 컴파일 타임 비용
9.1 런타임 비용
constexpr로 컴파일 타임에 계산된 값은 런타임 비용이 0입니다. constexpr auto V = make_vector();의 V는 컴파일 시점에 이미 초기화된 객체이므로, 프로그램 시작 시 추가 계산이 없습니다.
9.2 컴파일 타임 비용
constexpr 평가가 복잡할수록 컴파일 시간이 늘어납니다. 대규모 constexpr std::vector/std::string 연산, 반복적인 std::sort 등은 컴파일을 느리게 할 수 있습니다.
| 패턴 | 컴파일 영향 |
|---|---|
| 단순 constexpr 함수 (팩토리얼 등) | 미미 |
| std::array + std::sort | 작음 |
| std::vector + 여러 push_back + sort | 중간 |
| 큰 문자열 파싱, 반복 new/delete | 큼 |
9.3 권장 사항
- 필요한 범위만 constexpr로 처리
- 너무 큰 데이터는 런타임 초기화 고려
- 빌드 시간이 중요하면 constexpr 사용량을 조절
constexpr 고급 적용 체크리스트
- C++20 이상 사용 (
-std=c++20) -
std::vector/std::stringconstexpr 사용 시 C++20 확인 - constexpr
new사용 시 반드시delete호출 - 컴파일 타임 검증은
static_assert와 함께 - 반복문 우선, 깊은 재귀 지양
- 컴파일 타임 전용 함수는
consteval고려 - 모든 코드 블록에
cpp언어 태그 사용
정리
| 항목 | 내용 |
|---|---|
| constexpr std::vector | C++20, push_back·resize·정렬 등 컴파일 타임 지원 |
| constexpr std::string | C++20, 파싱·연결·검색 등 컴파일 타임 지원 |
| constexpr 알고리즘 | C++20, sort·find·copy·binary_search 등 |
| constexpr new/delete | C++20, 평가 종료 시 자동 해제 |
| 자주 발생하는 에러 | C++17 사용, throw, new 누락, I/O 등 |
| 베스트 프랙티스 | static_assert 검증, 반복문 선호, consteval 활용 |
| 프로덕션 패턴 | 설정 파싱, 명령어 테이블, Base64, 레지스터 검증 |
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ constexpr 완벽 가이드 | 컴파일 타임 계산·if constexpr·consteval 실전
- C++ 컴파일 타임 프로그래밍 기법 | 런타임 오버헤드 제거와 constexpr·consteval 실전
- C++ 컴파일 타임 최적화 | constexpr·PCH·모듈·ccache·Unity 빌드 [#15-3]
이 글에서 다루는 키워드
C++ constexpr 고급, constexpr vector, constexpr string, constexpr 알고리즘, constexpr new delete, C++20 constexpr, 컴파일 타임 컨테이너 등으로 검색하시면 이 글이 도움이 됩니다.
한 줄 요약: C++20 constexpr std::vector·std::string·알고리즘·new/delete로 컴파일 타임에 동적 컨테이너와 알고리즘을 실행할 수 있습니다. 설정 파싱, 명령어 테이블, Base64 디코딩 등 실전 패턴에 활용하세요.
실전 체크리스트
실무에서 이 개념을 적용할 때 확인해야 할 사항입니다.
코드 작성 전
- 이 기법이 현재 문제를 해결하는 최선의 방법인가?
- 팀원들이 이 코드를 이해하고 유지보수할 수 있는가?
- 성능 요구사항을 만족하는가?
코드 작성 중
- 컴파일러 경고를 모두 해결했는가?
- 엣지 케이스를 고려했는가?
- 에러 처리가 적절한가?
코드 리뷰 시
- 코드의 의도가 명확한가?
- 테스트 케이스가 충분한가?
- 문서화가 되어 있는가?
이 체크리스트를 활용하여 실수를 줄이고 코드 품질을 높이세요.
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. C++20 constexpr std::vector·std::string·알고리즘, constexpr new/delete, 컴파일 타임 컨테이너·알고리즘 완전 예제. 문제 시나리오, 자주 발생하는 에러, 베스트 프랙티… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
이전 글: C++ constexpr 기초 (#43-1)
관련 글
- C++ constexpr 완벽 가이드 | 컴파일 타임 계산·if constexpr·consteval 실전
- C++ 고성능 RPC 시스템: gRPC와 Protocol Buffers를 이용한 마이크로서비스 구축
- C++ 보안 코딩 가이드: 오버플로우 방지와 암호화 라이브러리(OpenSSL) 실전 연동 [#43-2]
- C++ Observability: Prometheus와 Grafana로 C++ 서버 모니터링 구축하기
- C++ 메타프로그래밍의 진화: Template에서 Constexpr, 그리고 Reflection까지