C++26 Static Reflection 완벽 가이드 | 컴파일 타임 타입 정보 활용
이 글의 핵심
C++26 Static Reflection의 핵심 개념과 실전 활용법. 컴파일 타임 타입 조회, 자동 직렬화, 코드 생성, ORM 매핑 등 메타프로그래밍을 혁신하는 새로운 기능을 상세히 설명합니다.
들어가며
C++26의 Static Reflection은 “템플릿 발명 이후 가장 큰 업그레이드”로 평가받는 기능입니다. 기존에는 타입 정보를 얻기 위해 매크로, SFINAE, 복잡한 템플릿 메타프로그래밍을 사용해야 했지만, 이제는 표준 라이브러리 함수로 간단히 조회할 수 있습니다.
이 글은 Static Reflection의 기본 문법, 실전 활용 패턴 (직렬화, ORM, 코드 생성), 기존 방식과의 비교를 코드 예제와 함께 설명합니다.
학습 전제 조건:
- C++ 템플릿 기본 (템플릿 가이드)
- constexpr 함수 이해
- 메타프로그래밍 개념
목차
Static Reflection이란?
개념
Reflection은 프로그램이 자신의 구조(타입, 멤버, 함수 등)를 조사하고 조작하는 능력입니다.
C++26 Static Reflection의 특징:
- 컴파일 타임 전용: 모든 연산이 컴파일 시점에 수행
- 제로 오버헤드: 런타임 성능 영향 없음
- 타입 안전: 컴파일러가 모든 오류 검출
- 표준 라이브러리:
<meta>헤더 제공
기존 방식의 한계
매크로 기반 (C++03 스타일):
#define REFLECT_STRUCT(Type, ...) \
/* 복잡한 매크로 마법 */
REFLECT_STRUCT(Person, name, age, email);
- 디버깅 어려움
- 타입 안전성 없음
- IDE 지원 부족
SFINAE/enable_if (C++11 스타일):
template<typename T>
auto serialize(const T& obj)
-> decltype(obj.name, void()) {
// name 멤버가 있을 때만 컴파일
}
- 읽기 어려움
- 에러 메시지 복잡
- 유지보수 어려움
C++26 Static Reflection:
template<typename T>
std::string serialize(const T& obj) {
std::string result = "{";
[:expand(nonstatic_data_members_of(^T)):] {
result += std::format("\"{}\": {}, ",
identifier_of(^^[::])),
obj.[::]
);
}
return result + "}";
}
- 읽기 쉬움
- 타입 안전
- 컴파일러 최적화 가능
기본 문법
Reflection Operator: ^
^ 연산자는 타입이나 표현식을 reflection으로 변환합니다:
#include <meta>
struct Person {
std::string name;
int age;
};
// 타입 reflection
constexpr auto person_reflection = ^Person;
// 멤버 reflection
constexpr auto name_reflection = ^Person::name;
// 표현식 reflection
int x = 42;
constexpr auto x_reflection = ^x;
Splicer: [: :]
**[: :]**는 reflection을 다시 코드로 변환합니다:
// 타입 이름 가져오기
constexpr auto type_name = identifier_of(^Person);
// type_name == "Person"
// 타입으로 변환
using PersonType = [:^Person:]; // Person과 동일
// 멤버 접근
Person p{"Alice", 30};
int age = p.[: ^Person::age :]; // p.age와 동일
기본 쿼리 함수
#include <meta>
#include <iostream>
struct Point {
double x;
double y;
void print() const {
std::cout << "(" << x << ", " << y << ")\n";
}
};
int main() {
// 타입 이름
std::cout << identifier_of(^Point) << '\n'; // "Point"
// 멤버 개수
constexpr auto members = nonstatic_data_members_of(^Point);
std::cout << members.size() << '\n'; // 2
// 멤버 함수 개수
constexpr auto methods = member_functions_of(^Point);
std::cout << methods.size() << '\n'; // 1
// 멤버 이름 출력
[:expand(members):] {
std::cout << identifier_of(^^[::]) << '\n';
}
// 출력: x, y
}
타입 조회
타입 정보 가져오기
#include <meta>
#include <iostream>
#include <string>
template<typename T>
void print_type_info() {
std::cout << "Type: " << identifier_of(^T) << '\n';
std::cout << "Size: " << sizeof(T) << " bytes\n";
std::cout << "Alignment: " << alignof(T) << " bytes\n";
// 타입 카테고리
if constexpr (is_class(^T)) {
std::cout << "Category: class\n";
} else if constexpr (is_enum(^T)) {
std::cout << "Category: enum\n";
} else {
std::cout << "Category: fundamental\n";
}
}
struct MyStruct {
int x;
double y;
};
int main() {
print_type_info<int>();
print_type_info<MyStruct>();
}
멤버 존재 확인
template<typename T>
constexpr bool has_name_member() {
constexpr auto members = nonstatic_data_members_of(^T);
bool found = false;
[:expand(members):] {
if (identifier_of(^^[::]) == "name") {
found = true;
}
}
return found;
}
struct Person { std::string name; int age; };
struct Point { double x; double y; };
static_assert(has_name_member<Person>()); // ✅ 통과
static_assert(!has_name_member<Point>()); // ✅ 통과
멤버 순회
모든 멤버 출력
#include <meta>
#include <iostream>
template<typename T>
void print_members(const T& obj) {
std::cout << identifier_of(^T) << " {\n";
[:expand(nonstatic_data_members_of(^T)):] {
std::cout << " " << identifier_of(^^[::])
<< " = " << obj.[::] << '\n';
}
std::cout << "}\n";
}
struct Config {
std::string host = "localhost";
int port = 8080;
bool ssl = false;
};
int main() {
Config cfg;
print_members(cfg);
// 출력:
// Config {
// host = localhost
// port = 8080
// ssl = 0
// }
}
조건부 멤버 처리
template<typename T>
void print_numeric_members(const T& obj) {
[:expand(nonstatic_data_members_of(^T)):] {
using MemberType = [:type_of(^^[::]):];
if constexpr (std::is_arithmetic_v<MemberType>) {
std::cout << identifier_of(^^[::])
<< " = " << obj.[::] << '\n';
}
}
}
struct Data {
std::string name; // 문자열 (건너뜀)
int count; // 숫자 (출력)
double value; // 숫자 (출력)
};
int main() {
Data d{"test", 42, 3.14};
print_numeric_members(d);
// 출력:
// count = 42
// value = 3.14
}
실전: 자동 직렬화
JSON 직렬화
#include <meta>
#include <string>
#include <sstream>
#include <vector>
template<typename T>
std::string to_json(const T& obj) {
std::ostringstream oss;
oss << "{";
bool first = true;
[:expand(nonstatic_data_members_of(^T)):] {
if (!first) oss << ", ";
first = false;
oss << "\"" << identifier_of(^^[::]) << "\": ";
using MemberType = [:type_of(^^[::]):];
const auto& value = obj.[::];
if constexpr (std::is_same_v<MemberType, std::string>) {
oss << "\"" << value << "\"";
} else if constexpr (std::is_arithmetic_v<MemberType>) {
oss << value;
} else if constexpr (std::is_same_v<MemberType, bool>) {
oss << (value ? "true" : "false");
}
}
oss << "}";
return oss.str();
}
struct User {
int id;
std::string name;
std::string email;
int age;
bool active;
};
int main() {
User user{1, "Alice", "[email protected]", 30, true};
std::cout << to_json(user) << '\n';
// {"id": 1, "name": "Alice", "email": "[email protected]", "age": 30, "active": true}
}
바이너리 직렬화
#include <meta>
#include <vector>
#include <cstring>
template<typename T>
std::vector<uint8_t> serialize_binary(const T& obj) {
std::vector<uint8_t> buffer;
[:expand(nonstatic_data_members_of(^T)):] {
using MemberType = [:type_of(^^[::]):];
const auto& value = obj.[::];
if constexpr (std::is_trivially_copyable_v<MemberType>) {
// POD 타입: 직접 복사
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&value);
buffer.insert(buffer.end(), ptr, ptr + sizeof(MemberType));
} else if constexpr (std::is_same_v<MemberType, std::string>) {
// 문자열: 길이 + 데이터
uint32_t len = value.size();
const uint8_t* len_ptr = reinterpret_cast<const uint8_t*>(&len);
buffer.insert(buffer.end(), len_ptr, len_ptr + sizeof(len));
buffer.insert(buffer.end(), value.begin(), value.end());
}
}
return buffer;
}
template<typename T>
T deserialize_binary(const std::vector<uint8_t>& buffer) {
T obj;
size_t offset = 0;
[:expand(nonstatic_data_members_of(^T)):] {
using MemberType = [:type_of(^^[::]):];
auto& value = obj.[::];
if constexpr (std::is_trivially_copyable_v<MemberType>) {
std::memcpy(&value, buffer.data() + offset, sizeof(MemberType));
offset += sizeof(MemberType);
} else if constexpr (std::is_same_v<MemberType, std::string>) {
uint32_t len;
std::memcpy(&len, buffer.data() + offset, sizeof(len));
offset += sizeof(len);
value.assign(
reinterpret_cast<const char*>(buffer.data() + offset),
len
);
offset += len;
}
}
return obj;
}
struct Message {
int id;
std::string content;
double timestamp;
};
int main() {
Message msg{42, "Hello, Reflection!", 1234567890.0};
// 직렬화
auto data = serialize_binary(msg);
std::cout << "Serialized size: " << data.size() << " bytes\n";
// 역직렬화
auto restored = deserialize_binary<Message>(data);
std::cout << "ID: " << restored.id << '\n';
std::cout << "Content: " << restored.content << '\n';
}
실전: ORM 매핑
SQL 쿼리 자동 생성
#include <meta>
#include <string>
#include <sstream>
template<typename T>
std::string generate_create_table() {
std::ostringstream oss;
oss << "CREATE TABLE " << identifier_of(^T) << " (\n";
bool first = true;
[:expand(nonstatic_data_members_of(^T)):] {
if (!first) oss << ",\n";
first = false;
oss << " " << identifier_of(^^[::]) << " ";
using MemberType = [:type_of(^^[::]):];
if constexpr (std::is_same_v<MemberType, int>) {
oss << "INTEGER";
} else if constexpr (std::is_same_v<MemberType, std::string>) {
oss << "TEXT";
} else if constexpr (std::is_same_v<MemberType, double>) {
oss << "REAL";
} else if constexpr (std::is_same_v<MemberType, bool>) {
oss << "BOOLEAN";
}
// 첫 번째 멤버를 PRIMARY KEY로
if (identifier_of(^^[::]) == "id") {
oss << " PRIMARY KEY";
}
}
oss << "\n);";
return oss.str();
}
struct User {
int id;
std::string name;
std::string email;
int age;
};
int main() {
std::cout << generate_create_table<User>() << '\n';
// CREATE TABLE User (
// id INTEGER PRIMARY KEY,
// name TEXT,
// email TEXT,
// age INTEGER
// );
}
INSERT 쿼리 생성
template<typename T>
std::string generate_insert(const T& obj) {
std::ostringstream oss;
oss << "INSERT INTO " << identifier_of(^T) << " (";
// 컬럼 이름
bool first = true;
[:expand(nonstatic_data_members_of(^T)):] {
if (!first) oss << ", ";
first = false;
oss << identifier_of(^^[::]);
}
oss << ") VALUES (";
// 값
first = true;
[:expand(nonstatic_data_members_of(^T)):] {
if (!first) oss << ", ";
first = false;
using MemberType = [:type_of(^^[::]):];
const auto& value = obj.[::];
if constexpr (std::is_same_v<MemberType, std::string>) {
oss << "'" << value << "'";
} else {
oss << value;
}
}
oss << ");";
return oss.str();
}
int main() {
User user{1, "Bob", "[email protected]", 25};
std::cout << generate_insert(user) << '\n';
// INSERT INTO User (id, name, email, age)
// VALUES (1, 'Bob', '[email protected]', 25);
}
실전: 코드 생성
Equality 연산자 자동 생성
template<typename T>
constexpr bool operator==(const T& lhs, const T& rhs) {
bool equal = true;
[:expand(nonstatic_data_members_of(^T)):] {
equal = equal && (lhs.[::] == rhs.[::]);
}
return equal;
}
struct Point {
double x;
double y;
};
int main() {
Point p1{1.0, 2.0};
Point p2{1.0, 2.0};
Point p3{3.0, 4.0};
std::cout << (p1 == p2) << '\n'; // 1 (true)
std::cout << (p1 == p3) << '\n'; // 0 (false)
}
해시 함수 자동 생성
#include <functional>
template<typename T>
struct ReflectionHash {
size_t operator()(const T& obj) const {
size_t seed = 0;
[:expand(nonstatic_data_members_of(^T)):] {
using MemberType = [:type_of(^^[::]):];
seed ^= std::hash<MemberType>{}(obj.[::]) +
0x9e3779b9 + (seed << 6) + (seed >> 2);
}
return seed;
}
};
struct Person {
std::string name;
int age;
};
int main() {
std::unordered_map<Person, std::string, ReflectionHash<Person>> map;
map[{"Alice", 30}] = "Engineer";
map[{"Bob", 25}] = "Designer";
}
ToString 자동 생성
template<typename T>
std::string to_string(const T& obj) {
std::ostringstream oss;
oss << identifier_of(^T) << " { ";
bool first = true;
[:expand(nonstatic_data_members_of(^T)):] {
if (!first) oss << ", ";
first = false;
oss << identifier_of(^^[::]) << ": " << obj.[::];
}
oss << " }";
return oss.str();
}
struct Config {
std::string host;
int port;
bool ssl;
};
int main() {
Config cfg{"localhost", 8080, true};
std::cout << to_string(cfg) << '\n';
// Config { host: localhost, port: 8080, ssl: 1 }
}
기존 방식과 비교
예제: 구조체 필드 개수 세기
C++17 (SFINAE):
template<typename T, typename = void>
struct field_count : std::integral_constant<size_t, 0> {};
template<typename T>
struct field_count<T, std::void_t<decltype(std::declval<T>().field1)>>
: std::integral_constant<size_t, 1> {};
// ... 각 필드마다 특수화 필요 (매우 복잡)
C++26 (Static Reflection):
template<typename T>
constexpr size_t field_count() {
return nonstatic_data_members_of(^T).size();
}
예제: 멤버 이름 가져오기
C++17 (매크로):
#define REFLECT_MEMBER(Type, member) \
template<> \
constexpr const char* get_member_name<Type, offsetof(Type, member)>() { \
return #member; \
}
REFLECT_MEMBER(Person, name);
REFLECT_MEMBER(Person, age);
// 모든 멤버마다 매크로 호출 필요
C++26 (Static Reflection):
template<typename T, size_t Index>
constexpr const char* get_member_name() {
constexpr auto members = nonstatic_data_members_of(^T);
return identifier_of(members[Index]);
}
코드 비교 요약
| 작업 | C++17 | C++26 |
|---|---|---|
| 멤버 개수 | 복잡한 SFINAE | nonstatic_data_members_of(^T).size() |
| 멤버 이름 | 매크로 필요 | identifier_of(^^member) |
| 멤버 타입 | decltype 트릭 | type_of(^^member) |
| 멤버 순회 | 불가능 또는 매크로 | [:expand(members):] |
| 가독성 | ⭐ | ⭐⭐⭐⭐⭐ |
| 유지보수성 | ⭐ | ⭐⭐⭐⭐⭐ |
성능 및 제약사항
성능 특성
컴파일 타임 비용:
- Reflection 연산은 컴파일 시간 증가
- 대규모 프로젝트에서 빌드 시간 주의
- 템플릿 인스턴스화와 유사한 비용
런타임 성능:
- 제로 오버헤드: 모든 연산이 컴파일 타임에 완료
- 생성된 코드는 손으로 작성한 코드와 동일
- 인라이닝, 최적화 모두 적용 가능
제약사항
1. Private 멤버 접근 제한
class MyClass {
private:
int secret; // Reflection으로 접근 불가
public:
int public_data; // 접근 가능
};
2. 동적 타입 불가
// ❌ 런타임 타입 정보 불가
void process(void* ptr, const std::type_info& type) {
// Reflection은 컴파일 타임 전용
}
// ✅ 템플릿으로 컴파일 타임 타입 전달
template<typename T>
void process(T* ptr) {
// Reflection 사용 가능
}
3. 멤버 함수 호출 제한
// 멤버 함수 조회는 가능하지만, 동적 호출은 제한적
constexpr auto methods = member_functions_of(^MyClass);
// 각 메서드의 시그니처는 알 수 있지만,
// 런타임에 문자열로 함수 이름 받아서 호출하는 것은 불가
고급 패턴
Visitor 패턴 자동 생성
template<typename T, typename Visitor>
void visit_members(T& obj, Visitor&& visitor) {
[:expand(nonstatic_data_members_of(^T)):] {
visitor(
identifier_of(^^[::]),
obj.[::]
);
}
}
struct Data {
int x;
double y;
std::string z;
};
int main() {
Data d{42, 3.14, "hello"};
visit_members(d, [](const char* name, auto& value) {
std::cout << name << " = " << value << '\n';
});
}
타입 변환 자동 생성
template<typename From, typename To>
To convert(const From& from) {
To to;
[:expand(nonstatic_data_members_of(^To)):] {
constexpr auto to_name = identifier_of(^^[::]);
// From에서 같은 이름의 멤버 찾기
[:expand(nonstatic_data_members_of(^From)):] {
if (identifier_of(^^[::]) == to_name) {
to.[: ^^members_of_To[i] :] = from.[::];
}
}
}
return to;
}
struct UserDTO {
int id;
std::string name;
std::string email;
};
struct User {
int id;
std::string name;
std::string email;
bool verified; // DTO에 없는 필드
};
int main() {
UserDTO dto{1, "Alice", "[email protected]"};
User user = convert<UserDTO, User>(dto);
// id, name, email은 자동 복사
// verified는 기본값
}
컴파일러 지원
현재 상태 (2026년 3월 기준)
| 컴파일러 | 버전 | 지원 상태 |
|---|---|---|
| GCC | 14+ | 실험적 지원 (-std=c++2c -freflection) |
| Clang | 19+ | 부분 지원 (-std=c++2c) |
| MSVC | 미정 | 개발 중 |
사용 방법
# GCC
g++ -std=c++2c -freflection source.cpp -o output
# Clang
clang++ -std=c++2c source.cpp -o output
실무 적용 가이드
단계별 도입
1단계: 간단한 유틸리티
- 디버그 출력 (
to_string) - 로깅 헬퍼
2단계: 직렬화
- JSON, XML, Binary 직렬화
- 네트워크 프로토콜
3단계: 프레임워크 통합
- ORM 매핑
- RPC 코드 생성
- 테스트 프레임워크
마이그레이션 전략
기존 매크로 코드:
#define SERIALIZE(Type) \
/* 복잡한 매크로 */
SERIALIZE(User);
SERIALIZE(Product);
Reflection으로 전환:
// 매크로 제거, 템플릿 함수로 대체
template<typename T>
std::string serialize(const T& obj) {
return to_json(obj); // Reflection 기반
}
// 모든 타입에 자동 적용
User user = ...;
Product product = ...;
std::cout << serialize(user) << '\n';
std::cout << serialize(product) << '\n';
마무리
C++26 Static Reflection은 메타프로그래밍의 패러다임을 바꾸는 기능입니다:
핵심 장점:
- 가독성: 복잡한 템플릿 트릭 → 명확한 코드
- 제로 오버헤드: 컴파일 타임 전용
- 타입 안전: 컴파일러 검증
- 생산성: 보일러플레이트 코드 자동 생성
주요 활용 분야:
- 직렬화/역직렬화 (JSON, Binary, XML)
- ORM, 데이터베이스 매핑
- RPC, 네트워크 프로토콜
- 테스트 프레임워크
- 코드 생성기
시작 방법:
- GCC 14+ 또는 Clang 19+ 설치
- 간단한
to_string함수부터 시작 - 프로젝트의 직렬화 코드를 Reflection으로 전환
- 점진적으로 확대 적용
다음 학습:
- C++ 템플릿 심화
- C++ Concepts
- C++ 메타프로그래밍
참고 자료: