스플라이스 지정자(splice specifier, C++26부터)

스플라이스 지정자(splice specifier, C++26부터)

C++26에 도입된 리플렉션(reflection)에서는, 리플렉션 값으로부터 언어 구조를 만들어 내는 것을 '스플라이싱(splicing)'이라고 불러요. **스플라이스 지정자(splice specifier)**는 리플렉션 값을 소스 코드에 '이어 붙여서' 언어 구조를 만들어 내는 구문이에요.

출처: cppreference

본문

문법

[: constant-expression :] (1)
typename [: constant-expression :] (2)
template [: constant-expression :] (3)
자리 의미
constant-expression std::meta::info 타입의 변환된 상수 표현식
  1. 의미가 문맥에 따라 달라지는 스플라이스 지정자.

  2. 타입 지정자(type specifier)인 스플라이스 지정자.

  3. 템플릿을 가리키는 스플라이스 지정자. 표현식으로 나타나지 않는다면, 그 지정자 뒤에는 <>로 둘러싼 템플릿 인자 목록이 와야 하고, 스코프 해석 연산자 ::의 왼쪽 피연산자여야 해요(예: template[:R:]::is_always_lock_free).

동작 방식(Explanation)

일반적으로 스플라이스 지정자는 constant-expression이 나타내는 언어 구조의 이름이나 값과 같은 의미를 가져요. 그것이 그 언어 구조를 **가리킨다(designate)**고 말해요.

표현식(Expression)

스플라이스 지정자 하나가 표현식이 되려면, 그 지정자는 typename 키워드 뒤에 나타나지 않아야 하고, 스코프 해석 연산자 :: 뒤에 오지도 않아야 해요.

struct S { static constexpr int a = 1; };
template<typename> struct TCls { static constexpr int b = 2; };

constexpr int c = [:^^S:]::a;              // [:^^S:]는 표현식이 아니다
constexpr int d = template [:^^TCls:]<int>::b; // template [:^^TCls:]<int>는 표현식이 아니다
template<auto V> constexpr int e = [:V:];  // [:V:]는 표현식이다
constexpr int f = template [:^^e:]<^^S::a>; // template [:^^e:]<^^S::a>는 표현식이다

constexpr auto g = typename [:^^int:](42); // typename [:^^int:]는 타입 지정자다

괄호로 묶지 않은 스플라이스 표현식은 템플릿 인자로 나타날 수 없어요.

스플라이스 표현식은 함수, 객체, 데이터 멤버, 변수, 구조적 바인딩, 값, 또는 열거자를 가리킬 수 있어요. 하지만 생성자, 소멸자, 이름 없는 비트 필드, 또는 이름을 붙이면 람다 표현식에 의해 캡처되는 지역 엔티티는 가리킬 수 없어요.

변수 또는 구조적 바인딩 V를 가리키는 스플라이스 표현식은, V가 정적(static) 또는 스레드(thread) 저장 기간을 가지거나 그 표현식을 둘러싸는 스코프에 선언되어 있을 때만 유효해요.

비정적 데이터 멤버 또는 암시적 객체 멤버 함수를 가리키는 스플라이스 표현식은 다음에서만 쓸 수 있어요.

  • 클래스 멤버 접근 표현식의 두 번째 피연산자로서
  • 멤버에 대한 포인터를 만들기 위한 주소 연산자의 피연산자로서
  • 비정적 데이터 멤버를 가리킨다면 비평가 피연산자 안에서

비정적 멤버의 이름과 달리, 스플라이스 표현식 [:r:]은 결코 this->[:r:]로 암시적으로 변환되지 않아요.

스플라이스 표현식 [:r:]은 직접 기반 클래스 관계 (D, B)를 기술할 수 있어요(예: rstd::meta::bases_of(^^D, ctx)의 요소라면). 직접 기반 클래스 관계는 클래스 멤버 접근 표현식의 두 번째 피연산자로만 나타날 수 있어요. 이 클래스 멤버 접근 표현식은 첫 피연산자를 "(아마 cv 한정된) D에 대한 참조"로 변환하고, 변환된 값의 B 직접 기반 클래스 부분 객체를 결과로 내요. 이를 통해 그렇지 않으면 모호하거나 접근할 수 없는 기반 클래스 부분 객체에 접근할 수 있어요.

스플라이스 표현식은 template 키워드를 쓸 때만 함수 템플릿이나 변수 템플릿을 가리킬 수 있어요.

타입(Type)

타입 지정자인 스플라이스 지정자는 스코프 해석 연산자 :: 뒤에 오면 안 되고, typename이 앞에 와야 하거나 타입 전용 문맥(type-only context)에 있어야 해요.

template<std::meta::info R> void tfn()
{
    typename [:R:]::type m; // OK, typename은 정규화된 이름에 적용된다
                            // (여기서 [:R:]은 스코프이지 타입 지정자가 아니다)
}
struct S { using type = int; };

void fn()
{
    [:^^S::type:] *var;          // 오류: [:^^S::type:]은 표현식이다
    typename [:^^S::type:] *var; // OK, int* 타입 변수를 선언한다
}

using alias = [:^^S::type:]; // OK, 타입 전용 문맥

스플라이스 타입 지정자는 타입, 클래스 템플릿, 또는 별칭 템플릿을 가리킬 수 있어요. 템플릿을 가리키면서 템플릿 인자 목록이 없다면 클래스 템플릿 인자 추론(CTAD)의 대상이 돼요.

스플라이스 지정자는 타입 요구사항(type requirement)에도 나타날 수 있어요.

template<typename T> concept C = requires
{
    typename [:T::r1:];      // T::r1이 타입의 리플렉션이 아니면 실패
    typename [:T::r2:]<int>; // T::r2가 어떤 템플릿 Z의 리플렉션이 아니어서
                             // Z<int>가 타입이 아니면 실패
};

스코프(Scope)

스코프 해석 연산자 ::의 왼쪽 피연산자인 스플라이스 지정자의 경우, 그 지정자가 템플릿 인자 목록을 가진다면 typename 또는 template이 앞에 와야 해요.

template<int V>
struct TCls
{
    static constexpr int s = V;
    using type = int;
};

int v1 = [:^^TCls<1>:]::s;
int v2 = template [:^^TCls:]<2>::s; // OK, 'template'은 스플라이스 지정자의 일부다
typename [:^^TCls:]<3>::type v3 = 3; // OK, 'typename'은 typename 지정자의 일부다
template [:^^TCls:]<3>::type v4 = 4; // OK, 'template'은 스플라이스 지정자의 일부다
typename template [:^^TCls:]<3>::type v5 = 5; // OK, v3과 같다
[:^^TCls:]<3>::type v6 = 6; // 오류: 예상하지 못한 <

스플라이스 지정자는 클래스 또는 열거형 타입, 또는 네임스페이스를 가리켜야 해요. [:r:]<TArgs...>:: 또는 template[:r:]<TArgs...>::의 스플라이스 지정자는 클래스 템플릿 또는 별칭 템플릿을 가리켜야 해요.

네임스페이스(Namespace)

네임스페이스 별칭 정의나 using 지시문에서의 스플라이스 지정자 [:r:]은, 전역 네임스페이스가 아닌 네임스페이스 이름 또는 네임스페이스 별칭을 가리켜야 해요. 종속적인(dependent) r은 네임스페이스 별칭 정의에는 나타날 수 있지만(using 지시문에는 없음), 이 경우 네임스페이스 별칭도 종속적이게 돼요.

주의할 점(Notes)

스플라이스 지정자 외에도 함수 템플릿 std::meta::extractstd::meta::substitute가 리플렉션을 그것이 나타내는 값으로 바꿀 수 있어요. 스플라이스 지정자와 달리 이들은 인자가 단독으로 상수 표현식일 것을 요구하지 않지만, 다른 제약이 있어요.

기능 테스트 매크로 표준 기능
__cpp_impl_reflection 202506L (C++26) 리플렉션(Reflection)

더 알아보기

  • 리플렉션 연산자 ^^: 리플렉션 값을 얻는 연산자.
  • std::meta::extract, std::meta::substitute: 리플렉션을 값으로 바꾸는 대안 도구.