void_t如何工作


148

我观看了Walter Brown在Cppcon14上有关现代模板编程的演讲(第一部分第二部分),他介绍了他的void_tSFINAE技术。

示例:
给定一个简单的变量模板,该模板评估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。这是为什么?(我不知道为什么需要匹配这些类型,难道不仅仅是工作需要任何默认类型吗?)


6
广告2)假设静态断言写为:has_member<A,int>::value。然后,评估为的局部专业has_member<A,void>无法匹配。因此,它必须是has_member<A,void>::valuetype的默认参数,或者与语法糖一起使用void
dyp 2014年

1
@dyp谢谢,我将对其进行编辑。嗯,我认为没有has_member< T , class = void >默认设置的必要void。假设此特征在任何时候都只能与1个模板参数一起使用,那么默认参数可以是任何类型吗?
不动声色

有趣的问题。
AStopher 2014年

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

Answers:


132

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,我们推导TA

在第二种模式中 void_t< decltype( T::member ) >,模板参数T出现在无法从任何模板参数推导出的上下文中。

有两个原因:

  • 内部表达式decltype明确排除在模板参数推导之外。我猜这是因为它可以任意复杂。

  • 即使我们使用的模式没有decltypelike 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:

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

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


3
谢谢!我已经读了一遍又一遍,我想我对模板实参推导如何正确工作以及编译器为最终模板选择什么的想法目前是不正确的。
不动声色2014年

1
@ JohannesSchaub-litb谢谢!不过,这有点令人沮丧。确实没有将模板参数与专业化匹配的规则吗?甚至没有明确的专业?
dyp

2
W / R / T默认模板参数,open-std.org
TC

1
@dyp几个星期后,阅读了很多关于这一点,从一个暗示这个片段,我想我开始了解如何工作的。您的解释使我从阅读到阅读更有意义,谢谢!
不动声色的2015年

1
我想补充一下,“ 主要模板”一词是关键(模板在代码中首次遇到)
毫无用处2016年

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>(因此我们使用了默认的定义:继承自false_type)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.