`void_t`如何工作

我观察了Walter Brown在Cppcon14关于现代模板编程( 第一部分 , 第二部分 )的演讲,他在那里介绍了他的void_t SFINAE技术。

例:
给定一个简单的variables模板,如果所有模板参数都格式正确,则该模板的计算结果为void

 template< class ... > using void_t = void; 

以及检查名为member的成员variables是否存在的特征:

 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 > ,因此它select专门的模板
  • has_member< T , void >并计算为true_type

2. has_member< B >

  • has_member< B , void_t< decltype( B::member ) > >
    • B::member不存在
    • decltype( B::member )forms不良并且默默无闻(sfinae)
    • 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使用的参数void_t ,才能工作。 这是为什么? (我不明白为什么这种types需要匹配,不只是任何默认types做这项工作?)

当您编写has_member<A>::value ,编译器查找名称has_member并find类模板,即此声明:

 template< class , class = void > struct has_member; 

(在OP中,这是作为一个定义写的)

将模板参数列表<A>与该主模板的模板参数列表进行比较。 由于主模板有两个参数,但只提供了一个参数,所以其余参数默认为默认模板参数: void 。 就好像你写了has_member<A, void>::value

现在,将模板参数列表与模板has_member任何特化进行has_member 。 只有在没有专业匹配的情况下,主模板的定义才被用作回退。 所以部分专业化是考虑到的:

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

编译器尝试将模板参数A, void与部分特化: Tvoid_t<..>定义的模式void_t<..> 。 首先,执行模板参数推导。 上面的局部特化仍然是一个模板参数模板,需要用参数“填充”。

第一个模式T允许编译器推导出模板参数T 这是一个微不足道的推论,但考虑一个像T const&的模式,我们仍然可以推导出T 对于模式T和模板参数A ,我们推导TA

在第二种模式中,模板参数出现在未推导的上下文中。 因此,我们不能从第二个模板参数void推导出模板参数T

模板参数推导完成(*) ,现在推导出的模板参数被replace。 这创build了一个看起来像这样的专业化:

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

现在可以评估void_t< decltype( A::member ) > >types。 代替后形成良好,不存在替代失败 。 我们得到:

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

现在,我们可以将这个专门化的模板参数列表与提供给原始has_member<A>::value的模板参数进行比较。 两种types完全匹配,所以select这个部分专业化。

另一方面,当我们将模板定义为:

 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> 。 参数与特殊化的参数不匹配,主模板被选为回退。


(*)标准,恕我直言,混淆,包括替代过程和模板参数推导过程中明确指定的模板参数的匹配。 例如(post-N4296)[temp.class.spec.match] / 2:

如果可以从实际的模板参数列表中推导出部分特化的模板参数,则部分特化与给定的实际模板参数列表匹配。

但这并不意味着必须推导出部分专业化的所有模板参数; 这也意味着replace必须成功,并且(似乎?)模板参数必须匹配部分特化的(替代)模板参数。 请注意,我并不完全知道标准指定替代参数列表和提供的参数列表之间的比较。

 // 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> (所以inheritance自true_type ),但是has_member<B, void>没有专门化(所以我们使用默认的定义:从false_typeinheritance)