我看了Walter Brown在Cppcon14上关于现代模板编程(Part I,Part II)的演讲,他在演讲中展示了他的void_t
SFINAE技术。
示例:
给定一个简单的变量模板,如果所有的模板参数都是格式良好的,则计算结果为void
:
template< class ... > using void_t = void;
以及以下trait,用于检查名为 * 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 >
- 1.**
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 >
- 2.**
has_member< B , void_t< decltype( B::member ) > >
B::member
不存在decltype( B::member )
不正确,并且以静默方式失败(sfinae)has_member< B , expression-sfinae >
,因此丢弃此模板- 编译器找到默认参数为void的
has_member< B , class = void >
has_member< B >
计算为false_type
http://ideone.com/HCTlBb
问题:
1.我的理解是否正确?
- Walter Brown指出,默认参数必须与
void_t
中使用的类型完全相同,才能正常工作。为什么会这样?(我不明白为什么这些类型需要匹配,难道不是任何默认类型都能完成这项工作吗?)
3条答案
按热度按时间2admgd591#
1.主类模板
当你写
has_member<A>::value
时,编译器会查找名字has_member
并找到 primary 类模板,也就是下面的声明:(In OP,这是作为定义写的。)
将模板参数列表
<A>
与此主模板的模板参数列表进行比较。由于主模板有两个参数,但您只提供了一个,因此剩余的参数默认为默认模板参数:void
。就好像你写了has_member<A, void>::value
。2.专用类模板
现在,模板参数列表与模板
has_member
的任何专门化进行比较。只有在没有匹配的专门化时,才使用主模板的定义作为后备。因此,考虑部分专业化:编译器尝试将模板参数
A, void
与部分特化中定义的模式相匹配:T
和void_t<..>
。***首先***进行模板参数推导。上面的部分专门化仍然是一个模板,其中的模板参数需要由实参“填充”。第一个模式
T
,允许编译器推导模板参数T
。这是一个微不足道的推论,但考虑像T const&
这样的模式,我们仍然可以推导出T
。对于模式T
和模板参数A
,我们推断T
为A
。在第二个模式
void_t< decltype( T::member ) >
中,模板参数T
出现在不能从任何模板参数推导出的上下文中。原因有二:
decltype
中的表达式被显式排除在模板参数推导之外。我想这是因为它可以任意复杂。decltype
的模式,如void_t< T >
,T
的推导也会发生在解析的别名模板上。也就是说,我们解析别名模板,然后尝试从结果模式中推断T
类型。然而,结果模式是void
,它不依赖于T
,因此不允许我们找到T
的特定类型。这类似于试图反转常数函数的数学问题(在这些术语的数学意义上)。模板参数推导完成(*),*现在*****推导的 * 模板参数被替换。这将创建一个特殊化,如下所示:
现在可以计算
void_t< decltype( A::member ) >
类型。它在替换后是格式良好的,因此,不会发生 * 替换失败 *。我们得到:3.选择
现在,我们可以将此专门化的模板参数列表与提供给原始
has_member<A>::value
的模板参数进行比较。这两种类型完全匹配,因此选择了这种部分专门化。另一方面,当我们将模板定义为:
我们最终得到相同的专业化:
但是我们的
has_member<A>::value
的模板参数列表现在是<A, int>
。参数与专门化的参数不匹配,主模板被选为后备模板。(*)令人困惑的是,该标准包括替换过程和 * 模板参数推导 * 过程中显式指定的模板参数的匹配。例如(post-N4296)[temp.class.spec.match]/2:
如果部分专门化的模板参数可以从实际模板参数列表推导出,则部分专门化匹配给定的实际模板参数列表。
但这并不意味着必须推导出部分特化的所有模板参数;这也意味着替换必须成功并且(看起来?模板变元必须匹配部分专门化的(替代的)模板参数。请注意,我并不完全清楚 where,标准指定了替代参数列表和提供的参数列表之间的比较。
gwo2fgha2#
上述特殊化仅在格式良好时才存在,因此当
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
)omhiaaxx3#
这个线程和线程SFINAE: Understanding void_t and detect_if救了我。我想通过一些例子来演示这种行为:
工具:cppinsights
按float类型和以下类型测试实现:
检测人
标准实施
从this std::void_t reference
输出量
案例1
输出量
因此,
has_type_member<T, std::void_t<typename T::type>>
定义了has_type_member
的特殊化,签名正好是has_type_member<T, void>
。案例二
输出:
这个例子说明编译器:
1.想找一个匹配
has_type_member<float>
1.发现模板需要2个参数,然后用默认参数填充第2个参数。结构体类似于
has_type_member<float, void>
1.找到此签名的特殊化并从
std::true_type
获取值案例三
输出:
case f
has_type_member<float>
完成为has_type_member<float, void>
。1.然后编译器尝试
typename float::type
失败。1.拾取的主模板。
case a
has_type_member<A>
已完成为has_type_member<A, void>
1.然后编译器尝试
has_type_member<A, typename A::type>
并发现它是has_type_member<A, int>
1.编译器决定它不是
has_type_member<A, void>
的规范1.然后拾取主模板。
案例B
has_type_member<B>
完成为has_type_member<B, void>
。1.然后编译器尝试
has_type_member<B, typename B::type>
,发现它是has_type_member<B, void>
。1.编译器决定它是
has_type_member<B, void>
的一个特殊化true_type
已拾取。案例4
输出:
has_type_member<T>
对于所有3个变量都是has_type_member<T, int>
类型,而true_type
的签名是has_type_member<T, void>
,如果它是有效的。总结
std::void_t
:1.检查
T::type
是否有效。1.如果只提供一个模板参数,则提供主模板的专门化。