\`void_t\` 是如何工作的

新手上路,请多包涵

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

例子:

给定一个简单的变量模板,如果所有模板参数格式正确,则计算结果为 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 ) 并且静默失败(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 中使用的类型完全相同,才能正常工作。这是为什么? (我不明白为什么这些类型需要匹配,不只是任何默认类型都可以完成这项工作吗?)

原文由 nonsensation 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 558
2 个回答

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 ,允许编译器推断模板参数 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
{ };

现在可以评估类型 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> 。参数与特化的参数不匹配,并且选择主模板作为后备。


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

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

但这 不仅仅 意味着必须推导出偏特化的所有模板参数;这也意味着替换必须成功并且(看起来?)模板参数必须匹配部分特化的(替换的)模板参数。请注意,我并不完全了解标准 在何处 指定替换参数列表和提供的参数列表之间的比较。

原文由 dyp 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

原文由 Jarod42 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题