在阅读Barry's answer至Check 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;
}
答案 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_t
将void
映射到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
进行编译。