[英]How does `void_t` work
我在 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
问题:
1.我的理解正确吗?
2. Walter Brown 声明默认参数必须与void_t
中使用的类型完全相同才能正常工作。 这是为什么? (我不明白为什么这些类型需要匹配,不只是任何默认类型都可以完成这项工作吗?)
当您编写has_member<A>::value
时,编译器会查找名称has_member
并找到主类模板,即此声明:
template< class , class = void >
struct has_member;
(在 OP 中,这是作为定义编写的。)
模板参数列表<A>
与此主模板的模板参数列表进行比较。 由于主模板有两个参数,但您只提供了一个,剩余参数默认为默认模板参数: void
。 就好像你写has_member<A, void>::value
。
现在,将模板参数列表与模板has_member
的任何特化进行比较。 仅当没有专业化匹配时,主模板的定义才用作后备。 所以考虑了偏特化:
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
编译器尝试将模板参数A, void
与部分特化中定义的模式: T
和void_t<..>
一一匹配。 首先,执行模板参数推导。 上面的部分特化仍然是一个带有模板参数的模板,需要用参数“填充”。
第一个模式T
允许编译器推导出模板参数T
。 这是一个微不足道的推论,但考虑像T const&
这样的模式,我们仍然可以推导出T
。 对于模式T
和模板参数A
,我们将T
推断为A
。
在第二个模式void_t< decltype( T::member ) >
中,模板参数T
出现在不能从任何模板参数推导出的上下文中。
有两个原因:
decltype
中的表达式被显式排除在模板参数推导之外。 我想这是因为它可以任意复杂。即使我们使用了像
void_t< T >
这样没有decltype
的模式,那么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
{ };
现在,我们可以将此特化的模板参数列表与提供给原始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:
如果可以从实际模板参数列表中推导出部分特化的模板参数,则部分特化匹配给定的实际模板参数列表。
但这不仅仅意味着必须推导出偏特化的所有模板参数; 这也意味着替换必须成功并且(看起来?)模板参数必须匹配部分特化的(替换的)模板参数。 请注意,我并不完全了解标准在何处指定替换参数列表和提供的参数列表之间的比较。
// 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
继承)
这个线程和线程SFINAE:Understanding void_t and detect_if救了我。 我想通过一些例子来展示这种行为:
工具: cppinsights
通过浮点类型和以下类型测试实现:
struct A {
using type = int;
};
struct B{
using type = void;
}
测试者
auto f = has_type_member<float>::value;
auto a = has_type_member<A>::value;
auto b = has_type_member<B>::value;
#include <type_traits>
// primary template handles types that have no nested ::type member:
template< class, class = int >
struct has_type_member : std::false_type { };
// specialization recognizes types that do have a nested ::type member:
template< class T >
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };
输出
bool f = false;
bool a = true;
bool b = true;
bool x = has_type_member<A, int>::value; //x = false;
#include <type_traits>
// primary template handles types that have no nested ::type member:
template< class, class = int >
struct has_type_member : std::false_type { };
template< class T >
struct has_type_member<T, void> : std::true_type { };
// specialization recognizes types that do have a nested ::type member:
template< class T >
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };
输出
/home/insights/insights.cpp:14:8: error: redefinition of 'has_type_member<T, std::void_t<typename T::type>>'
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/insights/insights.cpp:8:8: note: previous definition is here
struct has_type_member<T, void>: std::true_type {};
^
1 error generated.
Error while processing /home/insights/insights.cpp.
因此, has_type_member<T, std::void_t<typename T::type>>
定义了has_type_member
的特化,并且签名正是has_type_member<T, void>
。
#include <type_traits>
template< class, class = void >
struct has_type_member : std::false_type { };
// specialize 2nd type as void
template< class T>
struct has_type_member<T, void> : std::true_type { };
输出:
bool f = true;
bool a = true;
bool b = true;
这个案例表明编译器:
has_type_member<float>
has_type_member<float, void>
std::true_type
得到了值#include <type_traits>
template< class, class = void >
struct has_type_member : std::false_type { };
template<class T>
struct has_type_member<T, typename T::type>: std::true_type {};
输出:
bool f = false;
bool a = false;
bool b = true;
has_type_member<float>
已完成为has_type_member<float, void>
。typename float::type
并失败了。has_type_member<A>
已完成为has_type_member<A, void>
has_type_member<A, typename A::type>
并发现它是has_type_member<A, int>
has_type_member<A, void>
的特殊化has_type_member<B>
已完成为has_type_member<B, void>
。has_type_member<B, typename B::type>
并发现它是has_type_member<B, void>
。has_type_member<B, void>
的一种特殊化true_type
。#include <type_traits>
//int as default 2nd argument
template< class, class = int >
struct has_type_member : std::false_type { };
template<class T>
struct has_type_member<T, std::void<typename T::type>>: std::true_type {};
输出:
bool f = false;
bool a = false;
bool b = false;
has_type_member<T>
是所有 3 个变量的has_type_member<T, int>
类型,而true_type
的签名为has_type_member<T, void>
如果它是有效的。
所以, std::void_t
:
T::type
是否有效。是正确的? 不需要 std::void_t
模板<typename T, typename = T> struct isDefaultConstruct : std::false_type{};
模板结构 isDefaultConstruct<T, decltype(T())> : std::true_type{};
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.