为什么void_t需要检查成员类型的存在?

时间:2015-07-31 12:07:13

标签: c++ templates c++11

在阅读Barry's answerCheck if a given type has a inner template rebind时,我想:

为什么我们需要GRANT ALL PRIVILEGES ON database.* TO 'myUser'@'%' IDENTIFIED BY 'newpassword'; FLUSH PRIVILEGES;

为什么以下有效?

void_t

输出

#include <iostream>

template <typename X, typename Y = X>
struct has_rebind : std::false_type {};

template <typename X>
struct has_rebind<X, typename X::template rebind<int>> : std::true_type {};

struct A { };
struct B { template <typename > struct rebind { }; };

int main() {
    std::cout << has_rebind<A>::value << std::endl;
    std::cout << has_rebind<B>::value << std::endl;
}

demo

3 个答案:

答案 0 :(得分:8)

void_t是一个黑客。

模板类专业化模式匹配的工作原理是主模板确定每个参数的种类(类型或值),默认值为:

template<class A, class B=A, class C=void>
struct whatever {};

要调用whatever<?...>,您必须将有效参数与whatever<?...>的主要特化匹配。

在通过该测试之后,各种专业化模式匹配。所以假设你想要指针专门化。

template<class T>
struct whatever<T*, T*, void> {

这里的template<?...>参数列表与之匹配:它只提供了一个&#34;自由变量列表&#34;。模式匹配位于 <?...> 类名后面的whatever模板参数列表中。每个参数依次匹配发送到whatever的参数,以及匹配&#34;自由变量&#34;的模式。 (class T以上)已确定。

诀窍是你可以在这里放置没有模式匹配的表达式,但依赖于其他模式匹配,然后生成一个新类型:

template<class T>
struct whatever<T*, typename std::add_const<T>::type*, void> {

第二个参数是(模板add_const)的依赖类型,因此不能进行模式匹配。一般来说,some_template<T>::type的结果可以是图灵完全非内射计算:C ++标准不需要反转,幸运的是编译器编写者。

编译器不会尝试 - 而是尝试从其他模板参数中确定T,然后用T替换该参数,并检查它是否与传递的类型匹配到whatever

这里的技巧是替换失败(在直接上下文中)不会产生错误 1 。如果std::add_const<T>没有名为::type的字段,而不是生成错误,而是说#34;嗯,这种模式并不匹配&#34;。它会把这种专业化作为候选人中的一个可行的专业。

这个功能在C ++的早期就被添加了,因为模板函数甚至可以贪婪地匹配基本类型,如果你传递了{{1},尝试使用派生类型(比如iterator::value_type)会失败} int。使用此功能,iterator没有int这一事实意味着&#34;这不是某些模板函数的有效重载&#34;而不是重载解析期间的语法错误。实际上,模板函数在没有任何超载的情况下几乎无用,所以他们添加了SFINAE - 替换失败不是错误。

一旦出现,人们开始滥用它。 ::value_type试图利用它。

void_t

template<class T> struct whatever<T*, T*, void_t<decltype(std::declval<T>().hello_world())>> { 获取其类型参数,并丢弃它们,并在依赖上下文中生成void_t (从而阻止表达式用于推导{{ 1}})。它首先评估类型参数 2 ,这意味着如果生成的类型在直接上下文中导致失败,则会导致替换失败。

有一点是,一旦你通过void喂它,所产生的类型并不重要。当我们定义T的主要特化时,我们可能甚至不知道void_t应该是什么类型。所以我们将其丢弃并将其转换为t.hello_world()。为了匹配特化,类型必须匹配。所以我们在主要专业化中将类型设置为whatever<?...>,在专门化中我们进行测试然后将其传递给void以丢弃类型结果。

我们也可以将void与编译时void_t表达式一起使用,当且仅当std::enable_if_t<?>为真时,它才会生成类型bool(否则会失败)在眼前的情况下)。

从某种意义上说,void将有效类型表达式映射到bool,将无效类型表达式映射到替换失败。 void_tvoid映射到enable_if_t,将true映射到替换失败。在这两种情况下,您需要在专业化中使用void类型来消费&#34;消费&#34;结果和匹配正确。

两者在SFINAE代码中都非常有用,这是一个强大的功能,可让您根据简单模式匹配以外的其他方式决定使用哪种特化。

1 最后我查了一下,this requirement was not explicitly in the standard! SFINAE规则适用于模板函数替换失败,并且每个人(包括编译器编写者和标准编写者)只是假设它应用于模板类替换失败。当然,标准很难读,我可能只是错过了它。

2 有一次,各种编译器都不同意false应该做的事情。如果传递的表达式是替换失败,则有些会失败:其他人会注意到表达式将被丢弃,并在检查替换失败之前将其丢弃。这在缺陷报告中得到澄清。

答案 1 :(得分:6)

在这些方面:

template <typename X, typename Y = X>
struct has_rebind : std::false_type {};

template <typename X>
struct has_rebind<X, typename X::template rebind<int>> : std::true_type {};

默认类型(X)和您为专业化提供的类型(typename X::template rebind<int>)必须是同一类型。由于void是一个非常好的“默认虚拟类型”,它通常用作默认类型,我们使用void_t来更改专业化中给出的内容

答案 2 :(得分:3)

您的示例不起作用,因为has_rebind<A>has_rebind<B>都有一个模板参数,因此第二个参数是默认的: has_rebind<A,A>has_rebind<B,B>。现在,您确实提供了has_rebind<X, X::rebind<int>>的特化,但在实例化has_rebind<B,B>时不使用该特化。

您需要typename Y = typename X::template rebind<int>之类的内容来触发您的专业化,但无法为A进行编译。