简体   繁体   English

`void_t` 是如何工作的

[英]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 >所以这个模板被丢弃
  • compiler finds has_member< B , class = void > with void as default argument编译器发现has_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?) (我不明白为什么这些类型需要匹配,不只是任何默认类型都可以完成这项工作吗?)

1. Primary Class Template 1.小学班级模板

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

2. Specialized Class Template 2. 专业类模板

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与部分特化中定义的模式: Tvoid_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 like void_t< T > , then the deduction of T happens on the resolved alias template.即使我们使用了像void_t< T >这样没有decltype的模式,那么T的推导也会发生在解析的别名模板上。 That is, we resolve the alias template and later try to deduce the type T from the resulting pattern.也就是说,我们解析别名模板,然后尝试从结果模式中推断出类型T The resulting pattern, however, is void , which is not dependent on T and therefore does not allow us to find a specific type for T .然而,生成的模式是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
{ };

3. Choice 3. 选择

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;

standard implmentataion标准实施

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;

case 1情况1

#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>

case 2案例2

#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:这个案例表明编译器:

  1. Wanted to find a match for has_type_member<float>想要找到匹配has_type_member<float>
  2. Found out that the template requires 2 arguments, then filled the 2nd argument by default arguments.发现模板需要 2 个参数,然后用默认参数填充第二个参数。 The struct was like has_type_member<float, void>该结构就像has_type_member<float, void>
  3. Found a specialization of this signature and got the value from std::true_type找到了这个签名的一个特化,并从std::true_type得到了值

case 3案例3

#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;

case f案例 f

  1. has_type_member<float> was completed into has_type_member<float, void> . has_type_member<float>已完成为has_type_member<float, void>
  2. Then the compiler tried typename float::type and failed.然后编译器尝试了typename float::type并失败了。
  3. The primary template picked.选择的主要模板。

case a案例一

  1. has_type_member<A> was completed into has_type_member<A, void> has_type_member<A>已完成为has_type_member<A, void>
  2. Then compiler tried 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>
  3. Compiler decided it was not a specilization of has_type_member<A, void>编译器认为它不是has_type_member<A, void>的特殊化
  4. Then primary template picked.然后选择主要模板。

case b案例b

  1. has_type_member<B> was completed into has_type_member<B, void> . has_type_member<B>已完成为has_type_member<B, void>
  2. Then compiler tried 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>
  3. Compiler decided it was a specilization of has_type_member<B, void>编译器认为它是has_type_member<B, void>的一种特殊化
  4. true_type picked.选择了true_type

case 4案例4

#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>如果它是有效的。

Conclusion结论

So, the std::void_t :所以, std::void_t

  1. Check if T::type valid.检查T::type是否有效。
  2. Provides a specialization of primary template if only one template argument provided.如果仅提供一个模板参数,则提供主模板的特化。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM