“ void _ t”是怎么工作的

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

例如:
给定一个简单的变量模板,如果所有模板参数都格式良好,则计算结果为 void:

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

以及以下特征,用于检查名为 成员的成员变量是否存在:

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 >

    • A::member是存在的
    • decltype( A::member )结构良好
    • void_t<>有效,计算结果为 void
  • 因此它选择了专门的模板
  • 评估为 true_type

2. has_member< B >

    • 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. 我的理解正确吗?
2.Walter Brown 指出,默认参数的类型必须与 void_t中使用的类型完全相同才能正常工作。为什么?(我不明白为什么这种类型需要匹配,不是任何默认类型都可以做到吗?)

22964 次浏览
// 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继承)

1. 小学课堂模板

编写 has_member<A>::value时,编译器查找名称 has_member并找到 初选类模板,即这个声明:

template< class , class = void >
struct has_member;

(在 OP 中,这是一个定义。)

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

2. 专业课程模板

现在 ,将模板参数列表与模板 has_member的任何专门化进行比较。只有在没有匹配的专门化时,才使用主模板的定义作为备用。因此,部分专业化被考虑在内:

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

编译器尝试将模板参数 A, void与部分专门化中定义的模式逐个匹配: Tvoid_t<..>第一,执行模板参数演绎。上面的部分专门化仍然是带有模板参数的模板,需要用参数“填充”。

第一个模式 T,允许编译器推断模板参数 T。这是一个微不足道的推导,但是考虑一个类似于 T const&的模式,其中我们仍然可以推导出 T。对于模式 T和模板参数 A,我们推断 TA

在第二个模式 void_t< decltype( T::member ) >中,模板参数 T出现在不能从任何模板参数推导出来的上下文中。

这有两个原因:

  • decltype中的表达式被明确排除在模板参数演绎之外。我想这是因为它可以是任意复杂的。

  • 即使我们使用没有 decltype的模式(如 void_t< T >) ,那么 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
{ };

3. 选择

现在 ,我们可以将此专门化的模板参数列表与提供给原始 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>。参数与专门化的参数不匹配,因此选择主模板作为备用。


(*) 标准,IMHO 混淆,包括替换过程和在 模板论点演绎法过程中明确指定的模板参数的匹配。例如(post-N4296)[ tem.class.spec.match ]/2:

部分专门化匹配给定的实际模板参数列表 如果可以推导出偏特化的模板参数 从实际的模板参数列表。

但是这并不意味着必须推导出部分专门化的所有模板参数; 它还意味着替换必须成功并且(看起来是这样的?)模板参数必须与部分专门化的(替代的)模板参数匹配。注意,我没有完全意识到 哪里标准指定了替代参数列表和提供的参数列表之间的比较。

这个线程和线程 SFINAE: 理解 void _ t 并检测 _ if救了我。我想通过一些例子来演示这个行为:

工具: Cppsights

按浮动类型和以下类型测试实现:

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;

标准实施标准实施标准

来自 这个 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 { };

输出

bool f = false;
bool a = true;
bool b = true;


bool x = has_type_member<A, int>::value; //x = false;

个案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 { };

输出

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

个案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 { };

产出:

bool f = true;
bool a = true;
bool b = true;

这个案例显示编译器:

  1. 想找到与 has_type_member<float>匹配的
  2. 发现模板需要2个参数,然后用默认参数填充第2个参数。结构就像 has_type_member<float, void>
  3. 找到了这个签名的特殊化,并从 std::true_type获得了值

个案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 {};

产出:

bool f = false;
bool a = false;
bool b = true;

案例 f

  1. has_type_member<float>完成进入 has_type_member<float, void>
  2. 然后编译器尝试了 typename float::type,但失败了。
  3. 主模板选择。

案件 a

  1. has_type_member<A>完成进入 has_type_member<A, void>
  2. 然后编译器尝试 has_type_member<A, typename A::type>,发现它是 has_type_member<A, int>
  3. 编译器认为它不是 has_type_member<A, void>的专门化
  4. 然后选择了主模板。

案件 B

  1. has_type_member<B>完成进入 has_type_member<B, void>
  2. 然后编译器尝试 has_type_member<B, typename B::type>,发现它是 has_type_member<B, void>
  3. 编译器认为它是 has_type_member<B, void>的一个专门化
  4. true_type接通了。

个案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 {};

产出:

bool f = false;
bool a = false;
bool b = false;

对于所有3个变量,has_type_member<T>的类型都是 has_type_member<T, int>,而如果有效的话,true_type的签名是 has_type_member<T, void>

结论

那么,std::void_t:

  1. 检查 T::type是否有效。
  2. 如果只提供一个模板参数,则提供主模板的专门化。