`void_t`是如何工作的?

non*_*ion 140 c++ templates sfinae c++14

我观看了Walter Brown在Cppcon14上关于现代模板编程(第一部分,第二部分)的演讲,他在演讲中展示了他的void_tSFINAE技术.

示例:
给定一个简单的变量模板,该模板计算void所有模板参数是否格式正确:

template< class ... > using void_t = void;
Run Code Online (Sandbox Code Playgroud)

以及检查是否存在名为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
{ };
Run Code Online (Sandbox Code Playgroud)

我试图理解为什么以及如何运作.因此一个小例子:

class A {
public:
    int member;
};

class B {
};

static_assert( has_member< A >::value , "A" );
static_assert( has_member< B >::value , "B" );
Run Code Online (Sandbox Code Playgroud)

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 ) 形成不良,默默地失败(sfinae)
    • has_member< B , expression-sfinae > 所以这个模板被丢弃了
  • 编译器has_member< B , class = void >将void作为默认参数查找
  • has_member< B > 评估为 false_type

http://ideone.com/HCTlBb

问题:
1.我对此的理解是否正确?
沃尔特·布朗说,默认参数必须与用于void_t工作的类型完全相同.这是为什么?(我不明白为什么这种类型需要匹配,不只是任何默认类型都能完成这项工作吗?)

dyp*_*dyp 123

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

template< class , class = void >
struct has_member;
Run Code Online (Sandbox Code Playgroud)

(在OP中,这是作为定义编写的.)

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

现在,将模板参数列表与模板的任何特化进行比较has_member.仅当没有专门化匹配时,主模板的定义才用作后备.因此,部分专业化被考虑在内:

template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
Run Code Online (Sandbox Code Playgroud)

编译器尝试将模板参数A, void与部分特化中定义的模式匹配:Tvoid_t<..>逐个进行.首先,执行模板参数推导.上面的部分特化仍然是一个模板参数模板,需要通过参数"填充".

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

在第二种模式中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
{ };
Run Code Online (Sandbox Code Playgroud)

void_t< decltype( A::member ) > >现在可以评估该类型.它在取代后形成良好,因此不会发生取代失败.我们得到:

template<>
struct has_member<A, void> : true_type
{ };
Run Code Online (Sandbox Code Playgroud)

现在,我们可以将此专业化的模板参数列表与提供给原始参数的模板参数进行比较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
{ };
Run Code Online (Sandbox Code Playgroud)

我们最终得到了相同的专业化:

template<>
struct has_member<A, void> : true_type
{ };
Run Code Online (Sandbox Code Playgroud)

但我们has_member<A>::value现在的模板参数列表是<A, int>.参数与特化的参数不匹配,主模板被选为后退.


(*)标准,恕我直言,令人困惑,包括替换过程和模板参数推导过程中明确指定的模板参数的匹配.例如(后N4296)[temp.class.spec.match]/2:

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

但这并不仅仅意味着必须推导出部分专业化的所有模板参数; 它还意味着替换必须成功并且(看起来如此?)模板参数必须匹配部分特化的(替换)模板参数.请注意,我不完全了解其中的标准规定了取代参数列表和提供的参数列表之间的比较.

  • 谢谢!我一遍又一遍地阅读它,我想我对模板参数推导如何正确工作以及编译器为最终模板选择的内容的思考目前还不正确. (2认同)
  • W/r/t默认模板参数,http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2008 (2认同)

Jar*_*d42 17

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

上述特化仅在形成良好时存在,因此何时decltype( T::member )有效且不模糊.专业化是has_member<T , void>评论中的状态.

写时has_member<A>,这是has_member<A, void>因为默认模板参数.

我们有专门化has_member<A, void>(所以继承true_type)但我们没有专门化has_member<B, void>(所以我们使用默认定义:继承自false_type)

  • 那么`void_t&lt;decltype(T::member)&gt;&gt;`只不过是编写`decltype(T::member, void())`的标准化/更清晰的方式? (4认同)