반사 연산자(reflection operator)

반사 연산자(reflection operator) (C++26)

C++26부터는 언어 구성 요소(타입, 이름, 네임스페이스 같은 것) 그 자체를 값처럼 다룰 수 있게 됐어요. 그 출발점이 반사 연산자(reflection operator) ^^예요. 단항 ^^ 연산자는 주어진 언어 구성 요소의 반사(reflection) 값을 만들어 내는데, 그 타입은 std::meta::info예요.

출처: cppreference

본문

문법

^^ :: (1)
^^ reflection-name (2)
^^ type-id (3)
^^ id-expression (4)
reflection-name 식별자(identifier). 선택적으로 한정(qualified)되거나 템플릿 구분자(template disambiguator)를 사용할 수 있어요. 이 형태가 type-id와 id-expression보다 우선해요.
type-id type-id
id-expression id-expression

^^의 피연산자를 문법적으로 형성할 수 있는 가장 긴 토큰 시퀀스가 파싱돼요. 그 결과가 템플릿을 나타내면, 그 표현식 바로 뒤에 <가 따라와서는 안 돼요.

static_assert(std::meta::is_type(^^int())); // ^^int()는 타입 int()를 나타냄
                                            // ^^int에 대한 함수 호출이 아님

template<bool> struct X {};
consteval bool operator<(std::meta::info, X<false>) { return false; }
consteval void g(std::meta::info r, X<false> xv) {
    r == ^^int && true; // error: ^^는 type-id int&&에 적용됨
    r == ^^int & true; // error: ^^는 type-id int&에 적용됨
    r == (^^int) && true; // OK
    r == ^^int &&&& true; // error: int &&&&는 유효한 type-id가 아님
    ^^X < xv; // error: 템플릿을 나타내는 반사 표현식 뒤에 <가 옴
    (^^X) < xv; // OK
    ^^X<true> < xv; // OK
}

형태별 동작(Explanation)

  1. ^^:: 표현식은 전역 네임스페이스를 나타내요.

  2. ^^reflection-name에서 reflection-name은 룩업되고, 그 표현은 다음과 같이 결정돼요.

  • 룩업이 using-선언자를 대체한 선언을 찾으면, 그 표현식은 유효하지 않아요.
  • 그 외에 룩업이 네임스페이스 별칭이나 네임스페이스, 개념(concept)을 찾으면, 그 결과는 해당 실체를 나타내요.
  • 그 외에 룩업이 템플릿을 찾으면:
    • 룩업이 주입 클래스 이름(injected-class-name)을 찾으면:
      • 템플릿 구분자가 있으면, 그 결과는 주입 클래스 이름이 가리키는 클래스 템플릿을 나타내요.
      • 그 외에는 주입 클래스 이름이 type-name으로 간주됐을 때 모호하지 않아야 해요(아니면 유효하지 않은 표현식). 그 결과는 그렇게 이름 붙은 타입을 나타내요.
    • 그 외에 룩업이 오버로드 집합(overload set)을 찾으면, 그 집합은 단 하나의 고유한 함수 템플릿에 대한 선언만 담고 있어야 해요. 그 결과는 그 함수 템플릿을 나타내요.
    • 그 외에 룩업이 클래스 템플릿이나 변수 템플릿, 별칭 템플릿(alias template)을 찾으면, 그 결과는 해당 템플릿을 나타내요.
  • 그 외에 룩업이 타입 템플릿 매개변수를 찾으면, 그 결과는 대응하는 인자의 타입을 나타내요.
  • 그 외에 룩업이 typedef 이름이나 타입 별칭을 찾으면, 그 결과는 해당 타입 별칭을 나타내요.
template<typename T>
struct S {
    static constexpr std::meta::info r = ^^T;
    using type = T;
};
static_assert(S<int>::r == ^^int); // OK
static_assert(^^S<int>::type != ^^int); // OK

typedef struct X {} Y;
typedef struct Z {} Z;
constexpr std::meta::info e = ^^Y; // OK, 타입 별칭 Y를 나타냄
constexpr std::meta::info f = ^^Z; // OK, 타입이 아니라 타입 별칭 Z를 나타냄
  • 그 외에 룩업이 클래스 타입이나 열거 타입을 찾으면, 그 결과는 해당 타입을 나타내요.
  • 그 외에 룩업이 익명 유니온(anonymous union)의 클래스 멤버를 찾으면, 그 결과는 해당 멤버를 나타내요.
  • 그 외에는 reflection-name을 id-expression으로 간주해요(아래 형태 (4)).
  1. ^^type-id 표현식은 다음과 같이 결정되는 실체를 나타내요.
  • type-id가 자리표시자 타입(예: auto)을 가리키면, 그 표현식은 유효하지 않아요.
  • 그 외에 type-id가 템플릿 인자 목록을 가진 별칭 템플릿을 부르면, 그 결과는 그렇게 이름 붙은 타입 별칭을 나타내요. (다른 형태의 타입 별칭 이름은 reflection-name으로 취급되며, 결과 역시 타입 별칭을 나타내요.)
  • 그 외의 경우 결과는 type-id가 나타내는 타입이에요.
  1. ^^id-expression 표현식은 다음과 같이 결정되는 실체를 나타내요.
  • id-expression이 다음을 가리키면 표현식은 유효하지 않아요.
    • 람다의 init-capture로 선언된 변수,
    • 함수 지역 사전 정의 변수(__func__),
    • requires 표현식이 선언한 지역 매개변수(local parameter),
    • 도입된 지점과 반사 표현식 사이에 람다 스코프가 끼어든 지역 실체(캡처 여부와 무관).
  • 그 외에 id-expression이 오버로드 집합을 가리키면, 오버로드 해석이 단 하나의 함수를 골라야 해요. 그 결과는 그 함수를 나타내요.
template<typename T> void fn() requires (^^T != ^^int);
template<typename T> void fn() requires (^^T == ^^int);
template<typename T> void fn() requires (sizeof(T) == sizeof(int));
constexpr std::meta::info a = ^^fn<char>; // OK
constexpr std::meta::info b = ^^fn<int>;  // error: 모호함

^^의 피연산자는 미평가 피연산자(unevaluated operand)예요.

주의할 점(Notes)

반사 값은 std::meta::reflect_constant, std::meta::reflect_object, std::meta::reflect_function이나 std::meta::info기본 초기화해서도 만들 수 있어요. 게다가 기존 std::meta::info 객체에 <meta> 함수를 호출해 얻을 수도 있어요.

타입 별칭이 type-id의 일부로 등장하면, 반사 연산자는 그 타입 별칭을 보존하지 않아요.

using T = int;
static_assert(^^T != ^^int);
static_assert(^^T& == ^^int&);
static_assert(^^T const == ^^const int);
기능 테스트 매크로 표준 기능
__cpp_impl_reflection 202506L (C++26) 반사(Reflection)

예제

다음 코드는 ^^가 어떤 종류의 실체를 나타낼 수 있는지를 종류별로 보여 줘요.

int arr[] = {1, 2, 3};
auto [a1, a2, a3] = arr;
[[=1]] void fn(int n);
enum Enum { A };
using Alias = int;
struct S { int mem; };
template<auto> struct TCls {};
template<auto> void TFn();
template<auto> int TVar;
template<auto N> using TAlias = TCls<N>;
template<auto> concept Concept = true;
namespace NS {}
namespace NSAlias = NS;

constexpr auto r_arr   = ^^arr; // 변수를 나타냄
constexpr auto r_sb    = ^^a3; // 구조화 바인딩을 나타냄
constexpr auto r_fn    = ^^fn; // 함수를 나타냄
constexpr auto r_enum  = ^^Enum::A; // 열거자를 나타냄
constexpr auto r_alias = ^^Alias; // 타입 별칭을 나타냄
constexpr auto r_type  = ^^S; // 타입을 나타냄
constexpr auto r_mem   = ^^S::mem; // 클래스 멤버를 나타냄
constexpr auto r_tcls  = ^^TCls; // 클래스 템플릿을 나타냄
constexpr auto r_tfn   = ^^TFn; // 함수 템플릿을 나타냄
constexpr auto r_tvar  = ^^TVar; // 변수 템플릿을 나타냄
constexpr auto r_ttype = ^^TAlias; // 별칭 템플릿을 나타냄
constexpr auto r_cncpt = ^^Concept; // 개념을 나타냄
constexpr auto r_ns    = ^^NS; // 네임스페이스를 나타냄
constexpr auto r_ns2   = ^^NSAlias; // 네임스페이스 별칭을 나타냄

int main() {}

더 알아보기