`void_t'如何工作 [英] How does `void_t` work

查看:109
本文介绍了`void_t'如何工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我观看了Walter Brown在Cppcon14上关于现代模板编程的演讲(第I部分),第二部分),其中他提出了 void_t SFINAE技术。



示例: br>
给定一个简单的变量模板,如果所有模板参数形式都很好,则求值为 void

 模板< class ...>使用void_t = void; 

和以下trait检查是否存在成员变量 member

  class,class = void> 
struct has_member:std :: false_type
{};

// special as has_member< T,void>或丢弃(sfinae)
模板<类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)

    • code> has_member<因此此模板被丢弃


  • 编译器找到 has_member<

  • has_member< B> 计算为 false_type



a href =http://ideone.com/HCTlBb> http://ideone.com/HCTlBb



问题:

1.我的理解是否正确?

2. Walter Brown声明,默认参数必须与 void_t 为它工作。这是为什么? (我不明白为什么这种类型需要匹配,不只是任何默认类型的工作吗?)

解决方案

当你写 has_member< A> :: value 时,编译器查找名称 has_member ,并找到主要类模板,也就是这个声明:

  class,class = void> 
struct has_member;

(在OP中,写作定义。)



将模板参数列表< A> 与该主模板的模板参数列表进行比较。由于主模板有两个参数,但你只提供一个,剩下的参数默认为默认的模板参数: void 。就像你写了 has_member



现在,模板参数列表与模板 has_member 的任何特殊化进行比较。只有没有特殊化匹配,主模板的定义才会用作后退。因此,部分专业化被考虑:

  template<类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



模板参数扣除完成(*),现在取代了模板参数。这将创建一个如下所示的特殊化:

 模板< 
struct has_member< A,void_t< decltype(A :: member)> > :true_type
{};

类型 void_t< decltype(A :: member)> > 现在可以评估。其在取代后形成良好,因此,不发生替代失败。我们得到:

 模板<> 
struct has_member< A,void> :true_type
{};



现在,我们可以比较这个特化的模板参数列表和提供给原始 has_member< A> :: value



另一方面,当我们将模板定义为:

  template< class,class = int> // < -  int here而不是void 
struct has_member:false_type
{};

template<类T>
struct has_member< T,void_t< decltype(T :: member)> > :true_type
{};

我们结束了同样的专业化:

 模板<> 
struct has_member< A,void> :true_type
{};

但我们的模板参数列表 has_member< A> :: value 现在是< A,int> 。参数与专业化的参数不匹配,并且选择主模板作为后退。






< sup>(*)标准,IMHO混乱地,包括替换过程和在模板参数推导过程中明确指定的模板参数的匹配。例如(post-N4296)[temp.class.spec.match] / 2:


部分特化与给定的实际模板参数匹配如果部分特化的模板参数可以从实际模板参数列表中推导出
,那么列表


但这不是 意味着必须推导部分特化的所有模板参数;它也意味着替换必须成功,并且(因为它似乎?)模板参数必须匹配部分专业化的(替换)模板参数。请注意,我并不完全知道其中标准指定了替换参数列表和提供的参数列表之间的比较。


I watched Walter Brown's talk at Cppcon14 about modern template programming (Part I, Part II) where he presented his void_t SFINAE technique.

Example:
Given a simple variable template that evaluates to void if all template arguments are well formed:

template< class ... > using void_t = void;

and the following trait that checks for the existence of a member variable called 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 >

  • has_member< A , void_t< decltype( A::member ) > >
    • A::member exists
    • decltype( A::member ) is well-formed
    • void_t<> is valid and evaluates to void
  • has_member< A , void > and therefore it chooses the specialized template
  • has_member< T , void > and evaluates to true_type

2. has_member< B >

  • has_member< B , void_t< decltype( B::member ) > >
    • B::member does not exist
    • decltype( B::member ) is ill-formed and fails silently (sfinae)
    • has_member< B , expression-sfinae > so this template is discarded
  • compiler finds has_member< B , class = void > with void as default argument
  • has_member< B > evaluates to false_type

http://ideone.com/HCTlBb

Questions:
1. Is my understanding of this correct?
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. 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:

template< class , class = void >
struct has_member;

(In the OP, that's written as a definition.)

The template argument list <A> is compared to the template parameter list of this primary template. Since the primary template has two parameters, but you only supplied one, the remaining parameter is defaulted to the default template argument: void. It's as if you had written has_member<A, void>::value.

Now, the template parameter list is compared against any specializations of the template 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. 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. This is a trivial deduction, but consider a pattern like T const&, where we could still deduce T. For the pattern T and the template argument A, we deduce T to be A.

In the second pattern, the template-parameter appears in a non-deduced context. Hence, we cannot deduce the template parameter T from the second template argument void.

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. 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. 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>. 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:

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.

这篇关于`void_t'如何工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆