정적 리플렉션 첫걸음 — ^^ 연산자와 std::meta::info
리플렉션이 없던 세상에서는 어떻게 했는가
Java나 C#, Python을 써봤다면 리플렉션이 낯설지 않을 겁니다. 객체의 타입을 런타임에 물어보고, 필드 이름을 뽑아내고, 없는 메서드를 동적으로 호출하는 그 기능입니다. C++는 이걸 여태 정식으로 지원한 적이 없습니다.
그래서 C++ 진영은 우회로를 만들어 썼습니다. nlohmann/json 같은 라이브러리로 구조체를 JSON으로 직렬화하려면, 매크로로 필드 이름을 문자열로 나열하거나(NLOHMANN_DEFINE_TYPE_INTRUSIVE(Point, x, y)), Boost.PFR처럼 컴파일러별 트릭에 기대거나, protobuf처럼 아예 별도 코드 생성기(protoc)를 빌드 파이프라인에 끼워 넣어야 했습니다.
이 방식들의 공통된 문제는 필드를 하나 추가할 때마다 사람이 매크로 목록도 같이 고쳐야 한다는 겁니다. 깜빡하면 컴파일은 되는데 새 필드만 직렬화에서 빠지는, 찾기 어려운 버그가 생깁니다. C++26의 정적 리플렉션은 이 동기화 문제 자체를 없애려는 시도입니다.
^^ 연산자 — 타입과 값을 "가리키는" 새로운 방법
C++26은 ^^(리플렉션 연산자, 흔히 "cat-ears operator"라고 부릅니다)와 <meta> 헤더의 std::meta::info 타입을 새로 추가합니다. ^^는 피연산자를 컴파일 타임 값인 std::meta::info 객체로 바꿉니다.
#include <meta>
struct Point { int x; int y; };
int global_var = 42;
consteval std::meta::info reflect_examples() {
constexpr std::meta::info r1 = ^^int; // 타입을 반영
constexpr std::meta::info r2 = ^^Point; // 사용자 정의 타입을 반영
constexpr std::meta::info r3 = ^^global_var; // 변수를 반영
constexpr std::meta::info r4 = ^^std::vector; // 클래스 템플릿을 반영
return r1;
}여기서 중요한 건 std::meta::info가 평범한 값이라는 점입니다. 함수 인자로 넘길 수 있고, 컨테이너에 담을 수 있고, constexpr 문맥에서 비교할 수도 있습니다. 리플렉션이 "특수한 문법"이 아니라 "타입 시스템 위에 얹힌 값 기반 API"로 설계된 셈입니다. 이 설계 방향은 처음 제안서(P1240)부터 지금의 P2996까지 일관되게 유지됐습니다.
consteval이 전제 조건인 이유
리플렉션 관련 함수는 전부 consteval입니다. 즉 컴파일 타임에만 실행되고 런타임에는 존재하지 않습니다. 이게 C++의 리플렉션이 Java의 리플렉션과 근본적으로 다른 지점입니다.
// std::meta 라이브러리 함수는 이런 식으로 동작한다 (개념적 시그니처)
consteval std::string_view name_of(std::meta::info r);
consteval std::vector<std::meta::info> members_of(std::meta::info r);
consteval std::meta::info type_of(std::meta::info r);Java에서 Class.getFields()를 호출하면 그건 런타임 비용이 있는 실제 호출입니다. C++26에서 members_of(^^Point)를 호출하면, 컴파일러가 컴파일 타임에 계산을 끝내고 그 결과를 바탕으로 최종적으로는 리플렉션이 전혀 없었던 것처럼 최적화된 코드를 생성합니다. 런타임 오버헤드가 0이라는 뜻입니다. 대신 그 대가로, 런타임에 동적으로 타입을 알아내는 것(예: 사용자가 업로드한 플러그인의 타입을 실행 중에 조사하는 것)은 여전히 못 합니다. C++의 리플렉션은 "메타프로그래밍의 확장"이지 "런타임 introspection"이 아닙니다.
스플라이스 — 값을 다시 코드로 되돌리기
리플렉션 값을 얻는 것만으로는 부족합니다. 그 값을 다시 실제 타입이나 표현식으로 "펼쳐야" 쓸모가 있습니다. 이 방향의 연산을 스플라이스(splice)라고 부르고, [: ... :] 문법을 씁니다.
consteval std::meta::info r = ^^int;
typename [: r :] x = 10; // typename [:^^int:] 는 int 와 같다 → int x = 10;리플렉션(^^)과 스플라이스([: :])는 서로 역연산입니다. ^^로 코드를 값으로 만들고, [: :]로 값을 다시 코드로 되돌립니다. 다음 장에서 이 왕복을 이용해 실제로 쓸모 있는 걸 만들어 보겠습니다 — 구조체 필드를 순회하며 자동으로 직렬화 코드를 생성하는 예제입니다.
지금 시점에서 이 문법이 낯설다고 걱정할 필요는 없습니다. Bjarne Stroustrup을 포함한 위원회 멤버들도 리플렉션 제안서 초기 버전에서 문법을 여러 차례 갈아엎었습니다. ^^와 [: :]가 최종 형태로 굳은 것도 비교적 최근(2024~2025년 개정본)의 일입니다.