C++26 Static Reflection 완벽 가이드 | 컴파일 타임 타입 정보 활용

C++26 Static Reflection 완벽 가이드 | 컴파일 타임 타입 정보 활용

이 글의 핵심

C++26 Static Reflection의 핵심 개념과 실전 활용법. 컴파일 타임 타입 조회, 자동 직렬화, 코드 생성, ORM 매핑 등 메타프로그래밍을 혁신하는 새로운 기능을 상세히 설명합니다.

들어가며

C++26의 Static Reflection은 “템플릿 발명 이후 가장 큰 업그레이드”로 평가받는 기능입니다. 기존에는 타입 정보를 얻기 위해 매크로, SFINAE, 복잡한 템플릿 메타프로그래밍을 사용해야 했지만, 이제는 표준 라이브러리 함수로 간단히 조회할 수 있습니다.

이 글은 Static Reflection의 기본 문법, 실전 활용 패턴 (직렬화, ORM, 코드 생성), 기존 방식과의 비교를 코드 예제와 함께 설명합니다.

학습 전제 조건:

  • C++ 템플릿 기본 (템플릿 가이드)
  • constexpr 함수 이해
  • 메타프로그래밍 개념

목차

  1. Static Reflection이란?
  2. 기본 문법
  3. 타입 조회
  4. 멤버 순회
  5. 실전: 자동 직렬화
  6. 실전: ORM 매핑
  7. 실전: 코드 생성
  8. 기존 방식과 비교
  9. 성능 및 제약사항
  10. 마무리

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++17C++26
멤버 개수복잡한 SFINAEnonstatic_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월 기준)

컴파일러버전지원 상태
GCC14+실험적 지원 (-std=c++2c -freflection)
Clang19+부분 지원 (-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, 네트워크 프로토콜
  • 테스트 프레임워크
  • 코드 생성기

시작 방법:

  1. GCC 14+ 또는 Clang 19+ 설치
  2. 간단한 to_string 함수부터 시작
  3. 프로젝트의 직렬화 코드를 Reflection으로 전환
  4. 점진적으로 확대 적용

다음 학습:

  • C++ 템플릿 심화
  • C++ Concepts
  • C++ 메타프로그래밍

참고 자료: