Programming

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

procodes 2020. 6. 23. 22:02
반응형

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


나는 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작동하기 위해 사용 된 것과 동일한 유형이어야한다고 말합니다 . 왜 그런 겁니까? (이 유형이 일치 해야하는 이유를 모르겠습니다. 기본 유형만이 작동하지 않습니까?)


작성 has_member<A>::value하면 컴파일러가 이름 has_member을 찾고 기본 클래스 템플릿, 즉이 선언을 찾습니다 .

template< class , class = void >
struct has_member;

(OP에서는 정의로 작성되었습니다.)

템플릿 인수 목록 <A>은이 기본 템플릿의 템플릿 매개 변수 목록과 비교됩니다. 기본 템플리트에는 두 개의 매개 변수가 있지만 하나만 제공 했으므로 나머지 매개 변수는 기본적으로 기본 템플리트 인수로 설정됩니다 void. 마치 당신이 쓴 것처럼입니다 has_member<A, void>::value.

이제 템플릿 매개 변수 목록이 템플릿의 모든 특수화와 비교 has_member됩니다. 일치하는 전문화가없는 경우에만 기본 템플릿의 정의가 대체로 사용됩니다. 따라서 부분 전문화가 고려됩니다.

template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };

The compiler tries to match the template arguments A, void with the patterns defined in the partial specialization: T and void_t<..> one by one. First, template argument deduction is performed. The partial specialization above is still a template with template-parameters that need to be "filled" by arguments.

The first pattern, T, allows the compiler to deduce the template-parameter T. This is a trivial deduction, but consider a pattern like T const&, where we could still deduce T. For the pattern T and the template argument A, we deduce T to be A.

In the second pattern void_t< decltype( T::member ) >, the template-parameter T appears in a context where it cannot be deduced from any template argument. There are two reasons for this:

  • The expression inside decltype is explicitly excluded from template argument deduction. I guess this is because it can be arbitrarily complex.

  • Even if we used a pattern without decltype like void_t< T >, then the deduction of T happens on the resolved alias template. That is, we resolve the alias template and then try to deduce the type T from the resulting pattern. The resulting pattern however is void, which is not dependent on T and therefore does not allow us to find a specific type for T. This is similar to the mathematical problem of trying to invert a constant function (in the mathematical sense of those terms).

Template argument deduction is finished(*), now the deduced template arguments are substituted. This creates a specialization that looks like this:

template<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };

The type void_t< decltype( A::member ) > > can now be evaluated. It is well-formed after substitution, hence, no Substitution Failure occurs. We get:

template<>
struct has_member<A, void> : true_type
{ };

Now, we can compare the template parameter list of this specialization with the template arguments supplied to the original has_member<A>::value. Both types match exactly, so this partial specialization is chosen.

On the other hand, when we define the template as:

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
{ };

We end up with the same specialization:

template<>
struct has_member<A, void> : true_type
{ };

but our template argument list for has_member<A>::value now is <A, int>. The arguments do not match the parameters of the specialization, and the primary template is chosen as a fall-back.


(*) The Standard, IMHO confusingly, includes the substitution process and the matching of explicitly specified template arguments in the template argument deduction process. For example (post-N4296) [temp.class.spec.match]/2:

A partial specialization matches a given actual template argument list if the template arguments of the partial specialization can be deduced from the actual template argument list.

But this does not just mean that all template-parameters of the partial specialization have to be deduced; it also means that substitution must succeed and (as it seems?) the template arguments have to match the (substituted) template parameters of the partial specialization. Note that I'm not completely aware of where the Standard specifies the comparison between the substituted argument list and the supplied argument list.


// specialized as has_member< T , void > or discarded (sfinae)
template<class T>
struct has_member<T , void_t<decltype(T::member)>> : true_type
{ };

That above specialization exists only when it is well formed, so when decltype( T::member ) is valid and not ambiguous. the specialization is so for has_member<T , void> as state in the comment.

When you write has_member<A>, it is has_member<A, void> because of default template argument.

And we have specialization for has_member<A, void> (so inherit from true_type) but we don't have specialization for has_member<B, void> (so we use the default definition : inherit from false_type)

참고URL : https://stackoverflow.com/questions/27687389/how-does-void-t-work

반응형