나는 Cppcon14에서 Walter Brown이 SFINAE 기술 을 발표 한 최신 템플릿 프로그래밍 ( Part I , Part II ) 에 대해 이야기하는 것을 보았다 void_t
.
예 : 모든 템플릿 인수가
올바른지 평가하는 간단한 변수 템플릿이 제공됩니다 void
.
template< class ... > using void_t = void;
그리고 member라는 멤버 변수가 있는지 확인하는 다음 특성 :
template< class , class = void >
struct has_member : std::false_type
{ };
// specialized as has_member< T , void > or discarded (sfinae)
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : std::true_type
{ };
나는 왜 그리고 어떻게 작동하는지 이해하려고 노력했다. 따라서 작은 예 :
class A {
public:
int member;
};
class B {
};
static_assert( has_member< A >::value , "A" );
static_assert( has_member< B >::value , "B" );
1. has_member< A >
has_member< A , void_t< decltype( A::member ) > >
A::member
존재decltype( A::member )
잘 구성되어있다void_t<>
유효하고 평가void
has_member< A , void >
따라서 전문화 된 템플릿을 선택합니다has_member< T , void >
평가true_type
2. has_member< B >
has_member< B , void_t< decltype( B::member ) > >
B::member
존재하지 않는다decltype( B::member )
잘못 형성되어 자동으로 실패 (정사각형)has_member< B , expression-sfinae >
이 템플릿은 삭제됩니다
- 컴파일러는
has_member< B , class = void >
void를 기본 인수로 찾습니다. has_member< B >
~에 평가하다false_type
질문 :
1. 이것에 대한 나의 이해가 맞습니까?
2. Walter Brown은 기본 인수가 void_t
작동하기 위해 사용 된 것과 동일한 유형이어야한다고 말합니다 . 왜 그런 겁니까? (이 유형이 왜 일치 해야하는지 알지 못합니다. 기본 유형만이 작동하지 않습니까?)
답변
1. 기본 클래스 템플릿
작성 has_member<A>::value
하면 컴파일러가 이름 has_member
을 찾고 기본 클래스 템플릿, 즉이 선언을 찾습니다 .
template< class , class = void >
struct has_member;
(OP에서는 정의로 작성되었습니다.)
템플릿 인수 목록 <A>
은이 기본 템플릿의 템플릿 매개 변수 목록과 비교됩니다. 기본 템플릿에는 두 개의 매개 변수가 있지만 하나만 제공했기 때문에 나머지 매개 변수는 기본 템플릿 인수로 기본 설정됩니다 void
. 마치 당신이 쓴 것처럼입니다 has_member<A, void>::value
.
2. 전문화 된 수업 템플릿
이제 템플릿 매개 변수 목록이 템플릿의 전문화 영역과 비교 has_member
됩니다. 일치하는 전문화가없는 경우에만 기본 템플릿의 정의가 대체로 사용됩니다. 따라서 부분 전문화가 고려됩니다.
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
컴파일러는 템플릿 인수 A, void
를 부분 특수화에 정의 된 패턴 T
과 void_t<..>
하나씩 일치 시키려고 시도합니다 . 먼저 템플릿 인수 공제를 수행합니다. 위의 부분 전문화는 여전히 인수로 “채워야”하는 템플릿 매개 변수를 가진 템플릿입니다.
첫 번째 패턴 T
은 컴파일러가 template-parameter를 추론 할 수있게합니다 T
. 이것은 사소한 추론이지만 T const&
, 우리가 여전히 추론 할 수있는 것과 같은 패턴을 고려하십시오 T
. 패턴 T
과 템플릿 인수에 대해서는 다음 과 같이 A
추론 T
합니다 A
.
두 번째 패턴 void_t< decltype( T::member ) >
에서 템플릿 매개 변수 T
는 템플릿 인수에서 추론 할 수없는 상황에 나타납니다.
이에 대한 두 가지 이유가 있습니다.
내부 표현식
decltype
은 템플릿 인수 공제에서 명시 적으로 제외됩니다. 나는 이것이 임의로 복잡 할 수 있기 때문이라고 생각합니다.
decltype
와 같은 패턴을 사용하지 않더라도 , 분석 된 별칭 템플릿void_t< T >
에서 추론이T
발생합니다. 즉, 별칭 템플릿을 확인하고 나중에T
결과 패턴에서 유형을 추론하려고 시도합니다 . 그러나 결과 패턴void
은에 의존T
하지 않으므로에 대한 특정 유형을 찾을 수 없습니다T
. 이는 상수 함수를 반전시키려는 수학 문제와 유사합니다 (수학적인 의미에서).
템플릿 인수 공제 완료 (*) , 지금 유추 템플릿 인수가 대체됩니다. 이것은 다음과 같은 전문화를 만듭니다.
template<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };
이제 유형 void_t< decltype( A::member ) >
을 평가할 수 있습니다. 대체 후에 올바르게 구성되므로 대체 실패 가 발생 하지 않습니다. 우리는 얻는다 :
template<>
struct has_member<A, void> : true_type
{ };
3. 선택
이제이 전문화의 템플릿 매개 변수 목록을 원본에 제공된 템플릿 인수와 비교할 수 있습니다 has_member<A>::value
. 두 유형 모두 정확히 일치하므로이 부분 전문화가 선택됩니다.
반면 템플릿을 다음과 같이 정의하면
template< class , class = int > // <-- int here instead of void
struct has_member : false_type
{ };
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
우리는 같은 전문화로 끝납니다.
template<>
struct has_member<A, void> : true_type
{ };
그러나 현재 템플릿 인수 목록 has_member<A>::value
은 <A, int>
입니다. 인수가 전문화의 매개 변수와 일치하지 않으며 기본 템플리트가 대체로 선택됩니다.
(*) 혼동스럽게도 표준 IMHO에는 템플릿 인수 공제 프로세스 에서 대체 프로세스 및 명시 적으로 지정된 템플릿 인수의 일치가 포함 됩니다. 예를 들어 (post-N4296) [temp.class.spec.match] / 2 :
부분 특수화의 템플리트 인수가 실제 템플리트 인수리스트에서 추론 될 수있는 경우 부분 특수화는 주어진 실제 템플리트 인수리스트와 일치합니다.
그러나 이것은하지 않습니다 단지 부분적인 전문화의 모든 템플릿 매개 변수를 추론해야하는 것을 의미한다; 또한 대체가 성공해야하고 (있는 것처럼) 템플리트 인수가 부분 특수화의 (대체) 템플리트 매개 변수와 일치해야 함을 의미합니다. 표준이 대체 인수 목록과 제공된 인수 목록 간의 비교를 지정하는 위치를 완전히 알지 못합니다 .
답변
// specialized as has_member< T , void > or discarded (sfinae)
template<class T>
struct has_member<T , void_t<decltype(T::member)>> : true_type
{ };
위의 전문화는 제대로 구성된 경우에만 존재하므로 decltype( T::member )
유효하고 모호하지 않은 경우 입니다. 전문화는 has_member<T , void>
주석의 상태와 동일합니다.
당신이 쓸 때 has_member<A>
, 그것은이다 has_member<A, void>
때문에 기본 템플릿 인수의.
그리고 우리는 has_member<A, void>
(부터 상속 true_type
)에 대한 전문화를 가지고 있지만 (우리는 has_member<B, void>
기본 정의 : inherit from false_type
)를 전문화하지 않습니다 .