[英]How does `void_t` work
I watched Walter Brown's talk at Cppcon14 about modern template programming ( Part I , Part II ) where he presented his void_t
SFINAE technique.我在 Cppcon14 上观看了 Walter Brown 关于现代模板编程的演讲(第一部分,第二部分),他展示了他的
void_t
SFINAE 技术。
Example:例子:
Given a simple variable template that evaluates to void
if all template arguments are well formed:给定一个简单的变量模板,如果所有模板参数格式正确,则计算结果为
void
:
template< class ... > using void_t = void;
and the following trait that checks for the existence of a member variable called member :以及检查是否存在名为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
{ };
I tried to understand why and how this works.我试图理解这是为什么以及如何工作的。 Therefore a tiny example:
因此,一个小例子:
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 >
has_member< A , void_t< decltype( A::member ) > >
A::member
exists A::member
存在decltype( A::member )
is well-formed decltype( A::member )
格式正确void_t<>
is valid and evaluates to void
void_t<>
有效并计算为void
has_member< A , void >
and therefore it chooses the specialized template has_member< A , void >
因此它选择了专门的模板has_member< T , void >
and evaluates to true_type
has_member< T , void >
并计算为true_type
2. has_member< B >
2.
has_member< B >
has_member< B , void_t< decltype( B::member ) > >
B::member
does not exist B::member
不存在decltype( B::member )
is ill-formed and fails silently (sfinae) decltype( B::member )
格式不正确并且静默失败 (sfinae)has_member< B , expression-sfinae >
so this template is discarded has_member< B , expression-sfinae >
所以这个模板被丢弃has_member< B , class = void >
with void as default argumenthas_member< B , class = void >
以 void 作为默认参数has_member< B >
evaluates to false_type
has_member< B >
计算结果为false_type
http://ideone.com/HCTlBb http://ideone.com/HCTlBb
Questions:问题:
1. Is my understanding of this correct? 1.我的理解正确吗?
2. Walter Brown states that the default argument has to be the exact same type as the one used in void_t
for it to work. 2. Walter Brown 声明默认参数必须与
void_t
中使用的类型完全相同才能正常工作。 Why is that?这是为什么? (I don't see why this types need to match, doesn't just any default type does the job?)
(我不明白为什么这些类型需要匹配,不只是任何默认类型都可以完成这项工作吗?)
When you write has_member<A>::value
, the compiler looks up the name has_member
and finds the primary class template, that is, this declaration:当您编写
has_member<A>::value
时,编译器会查找名称has_member
并找到主类模板,即此声明:
template< class , class = void >
struct has_member;
(In the OP, that's written as a definition.) (在 OP 中,这是作为定义编写的。)
The template argument list <A>
is compared to the template parameter list of this primary template.模板参数列表
<A>
与此主模板的模板参数列表进行比较。 Since the primary template has two parameters, but you only supplied one, the remaining parameter is defaulted to the default template argument: void
.由于主模板有两个参数,但您只提供了一个,剩余参数默认为默认模板参数:
void
。 It's as if you had written has_member<A, void>::value
.就好像你写
has_member<A, void>::value
。
Now , the template parameter list is compared against any specializations of the template has_member
.现在,将模板参数列表与模板
has_member
的任何特化进行比较。 Only if no specialization matches, the definition of the primary template is used as a fall-back.仅当没有专业化匹配时,主模板的定义才用作后备。 So the partial specialization is taken into account:
所以考虑了偏特化:
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
The compiler tries to match the template arguments A, void
with the patterns defined in the partial specialization: T
and void_t<..>
one by one.编译器尝试将模板参数
A, void
与部分特化中定义的模式: T
和void_t<..>
一一匹配。 First , template argument deduction is performed.首先,执行模板参数推导。 The partial specialization above is still a template with template-parameters that need to be "filled" by arguments.
上面的部分特化仍然是一个带有模板参数的模板,需要用参数“填充”。
The first pattern T
, allows the compiler to deduce the template-parameter T
.第一个模式
T
允许编译器推导出模板参数T
。 This is a trivial deduction, but consider a pattern like T const&
, where we could still deduce T
.这是一个微不足道的推论,但考虑像
T const&
这样的模式,我们仍然可以推导出T
。 For the pattern T
and the template argument A
, we deduce T
to be A
.对于模式
T
和模板参数A
,我们将T
推断为A
。
In the second pattern void_t< decltype( T::member ) >
, the template-parameter T
appears in a context where it cannot be deduced from any template argument.在第二个模式
void_t< decltype( T::member ) >
中,模板参数T
出现在不能从任何模板参数推导出的上下文中。
There are two reasons for this:
有两个原因:
The expression inside
decltype
is explicitly excluded from template argument deduction.decltype
中的表达式被显式排除在模板参数推导之外。 I guess this is because it can be arbitrarily complex.我想这是因为它可以任意复杂。
Even if we used a pattern without
decltype
likevoid_t< T >
, then the deduction ofT
happens on the resolved alias template.即使我们使用了像
void_t< T >
这样没有decltype
的模式,那么T
的推导也会发生在解析的别名模板上。 That is, we resolve the alias template and later try to deduce the typeT
from the resulting pattern.也就是说,我们解析别名模板,然后尝试从结果模式中推断出类型
T
The resulting pattern, however, isvoid
, which is not dependent onT
and therefore does not allow us to find a specific type forT
.然而,生成的模式是
void
,它不依赖于T
,因此不允许我们找到T
的特定类型。 This is similar to the mathematical problem of trying to invert a constant function (in the mathematical sense of those terms).这类似于试图反转一个常数函数的数学问题(在这些术语的数学意义上)。
Template argument deduction is finished (*) , now the deduced template arguments are substituted.模板参数推导完成(*) ,现在替换推导的模板参数。 This creates a specialization that looks like this:
这将创建一个如下所示的专业化:
template<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };
The type void_t< decltype( A::member ) >
can now be evaluated.现在可以评估类型
void_t< decltype( A::member ) >
。 It is well-formed after substitution, hence, no Substitution Failure occurs.它在替换后是良构的,因此不会发生替换失败。 We get:
我们得到:
template<>
struct has_member<A, void> : true_type
{ };
Now , we can compare the template parameter list of this specialization with the template arguments supplied to the original has_member<A>::value
.现在,我们可以将此特化的模板参数列表与提供给原始
has_member<A>::value
的模板参数进行比较。 Both types match exactly, so this partial specialization is chosen.两种类型都完全匹配,因此选择了这种部分特化。
On the other hand, when we define the template as:另一方面,当我们将模板定义为:
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
{ };
We end up with the same specialization:我们最终得到了相同的专业化:
template<>
struct has_member<A, void> : true_type
{ };
but our template argument list for has_member<A>::value
now is <A, int>
.但是我们的
has_member<A>::value
的模板参数列表现在是<A, int>
。 The arguments do not match the parameters of the specialization, and the primary template is chosen as a fall-back.参数与特化的参数不匹配,并且选择主模板作为后备。
(*) The Standard, IMHO confusingly, includes the substitution process and the matching of explicitly specified template arguments in the template argument deduction process. (*)恕我直言,该标准令人困惑,包括替换过程和模板参数推导过程中显式指定模板参数的匹配。 For example (post-N4296) [temp.class.spec.match]/2:
例如(N4296 后)[temp.class.spec.match]/2:
A partial specialization matches a given actual template argument list if the template arguments of the partial specialization can be deduced from the actual template argument list.
如果可以从实际模板参数列表中推导出部分特化的模板参数,则部分特化匹配给定的实际模板参数列表。
But this does not just mean that all template-parameters of the partial specialization have to be deduced;但这不仅仅意味着必须推导出偏特化的所有模板参数; it also means that substitution must succeed and (as it seems?) the template arguments have to match the (substituted) template parameters of the partial specialization.
这也意味着替换必须成功并且(看起来?)模板参数必须匹配部分特化的(替换的)模板参数。 Note that I'm not completely aware of where the Standard specifies the comparison between the substituted argument list and the supplied argument list.
请注意,我并不完全了解标准在何处指定替换参数列表和提供的参数列表之间的比较。
// specialized as has_member< T , void > or discarded (sfinae)
template<class T>
struct has_member<T , void_t<decltype(T::member)>> : true_type
{ };
That above specialization exists only when it is well formed, so when decltype( T::member )
is valid and not ambiguous.上述特化仅在格式正确时才存在,因此当
decltype( T::member )
有效且不模棱两可时。 the specialization is so for has_member<T , void>
as state in the comment. has_member<T , void>
的专业化是评论中的状态。
When you write has_member<A>
, it is has_member<A, void>
because of default template argument.当您编写
has_member<A>
时,它是has_member<A, void>
因为默认模板参数。
And we have specialization for has_member<A, void>
(so inherit from true_type
) but we don't have specialization for has_member<B, void>
(so we use the default definition : inherit from false_type
)我们对
has_member<A, void>
有特化(所以从true_type
继承)但我们对has_member<B, void>
没有特化(所以我们使用默认定义:从false_type
继承)
This thread and the thread SFINAE: Understanding void_t and detect_if saved me.这个线程和线程SFINAE:Understanding void_t and detect_if救了我。 I want to demonstrate the behavior by some examples:
我想通过一些例子来展示这种行为:
The tool: cppinsights工具: cppinsights
To test the implmentation by types of float and following types:通过浮点类型和以下类型测试实现:
struct A {
using type = int;
};
struct B{
using type = void;
}
Tested by测试者
auto f = has_type_member<float>::value;
auto a = has_type_member<A>::value;
auto b = has_type_member<B>::value;
From this std::void_t reference从这个 std::void_t 参考
#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 { };
Output输出
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 { };
Output输出
/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.
So, has_type_member<T, std::void_t<typename T::type>>
defined a specialization of has_type_member
and the signature is exactly has_type_member<T, void>
.因此,
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 { };
Output:输出:
bool f = true;
bool a = true;
bool b = true;
This case shows that the compiler:这个案例表明编译器:
has_type_member<float>
has_type_member<float>
has_type_member<float, void>
has_type_member<float, void>
std::true_type
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 {};
Output:输出:
bool f = false;
bool a = false;
bool b = true;
has_type_member<float>
was completed into has_type_member<float, void>
. has_type_member<float>
已完成为has_type_member<float, void>
。typename float::type
and failed.typename float::type
并失败了。has_type_member<A>
was completed into has_type_member<A, void>
has_type_member<A>
已完成为has_type_member<A, void>
has_type_member<A, typename A::type>
and found out it was has_type_member<A, int>
has_type_member<A, typename A::type>
并发现它是has_type_member<A, int>
has_type_member<A, void>
has_type_member<A, void>
的特殊化has_type_member<B>
was completed into has_type_member<B, void>
. has_type_member<B>
已完成为has_type_member<B, void>
。has_type_member<B, typename B::type>
and found out it was has_type_member<B, void>
.has_type_member<B, typename B::type>
并发现它是has_type_member<B, void>
。has_type_member<B, void>
has_type_member<B, void>
的一种特殊化true_type
picked.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 {};
Output:输出:
bool f = false;
bool a = false;
bool b = false;
The has_type_member<T>
is of type has_type_member<T, int>
for all 3 variables, while the true_type
has signature as has_type_member<T, void>
if it's valid. has_type_member<T>
是所有 3 个变量的has_type_member<T, int>
类型,而true_type
的签名为has_type_member<T, void>
如果它是有效的。
So, the std::void_t
:所以,
std::void_t
:
T::type
valid.T::type
是否有效。is correct?是正确的? no need std::void_t
不需要 std::void_t
template<typename T, typename = T> struct isDefaultConstruct : std::false_type{};模板<typename T, typename = T> struct isDefaultConstruct : std::false_type{};
template struct isDefaultConstruct<T, decltype(T())> : std::true_type{};模板结构 isDefaultConstruct<T, decltype(T())> : std::true_type{};
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.