`void_t`는 어떻게 작동합니까


149

나는 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

http://ideone.com/HCTlBb

질문 :
1. 이것에 대한 나의 이해가 맞습니까?
2. Walter Brown은 기본 인수가 void_t작동하기 위해 사용 된 것과 동일한 유형이어야한다고 말합니다 . 왜 그런 겁니까? (이 유형이 왜 일치 해야하는지 알지 못합니다. 기본 유형만이 작동하지 않습니까?)


6
Ad 2) 정적 어설 션이 다음과 같이 작성되었다고 상상해보십시오 has_member<A,int>::value. 그런 다음 평가 된 부분 전문화가 has_member<A,void>일치하지 않습니다. 따라서 has_member<A,void>::value구문 설탕 인 경우 type의 기본 인수 여야 합니다 void.
dyp December

1
@dyp 감사합니다. 편집하겠습니다. Mh, 아직 has_member< T , class = void >기본 설정 이 필요하지 않습니다 void. 이 특성이 언제든지 하나의 템플리트 인수와 함께 사용된다고 가정하면 기본 인수는 모든 유형이 될 수 있습니까?
nonsensation

흥미로운 질문입니다.
AStopher

2
이 제안에서 open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4436.pdf 로 Walter는로 변경 template <class, class = void>되었습니다 template <class, class = void_t<>>. 이제 우리는 void_t별칭 템플릿 구현으로 원하는 것을 자유롭게 할 수 있습니다 :)
JohnKoch

답변:


133

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를 부분 특수화에 정의 된 패턴 Tvoid_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 :

부분 특수화의 템플리트 인수가 실제 템플리트 인수리스트에서 추론 될 수있는 경우 부분 특수화는 주어진 실제 템플리트 인수리스트와 일치합니다.

그러나 이것은하지 않습니다 단지 부분적인 전문화의 모든 템플릿 매개 변수를 추론해야하는 것을 의미한다; 또한 대체가 성공해야하고 (있는 것처럼) 템플리트 인수가 부분 특수화의 (대체) 템플리트 매개 변수와 일치해야 함을 의미합니다. 표준이 대체 인수 목록과 제공된 인수 목록 간의 비교를 지정하는 위치를 완전히 알지 못합니다 .


3
감사합니다! 나는 그것을 반복해서 읽었으며 템플릿 인수 공제가 정확히 어떻게 작동하고 컴파일러가 최종 템플릿을 위해 선택하는 것이 현재 정확하지 않다고 생각합니다.
nonsensation

1
@ JohannesSchaub-litb 감사합니다! 그래도 약간 우울합니다. 템플릿 인수를 전문화와 일치시키는 규칙이 실제로 있습니까? 명시적인 전문화가 아닌가?
dyp

2
기본 템플릿 인수 W / r / t, open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2008
TC

1
@dyp 몇 주 후에 이것에 대해 많은 것을 읽고이 발췌 문장 의 힌트와 함께 이것이 어떻게 작동하는지 이해하기 시작한다고 생각합니다. 당신의 설명은 읽기에서 나에게 더 의미있는 읽기를 만듭니다, 감사합니다!
nonsensation

1
나는 용어 것을 추가하고 싶었 템플릿 키 (템플릿 코드의 첫 만남)이었다
nonsensation을

18
// 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)를 전문화하지 않습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.