我本来也想问这个问题,但已经有人问过了。不幸的是,现有的答案都没有真正回答这个问题。至少对我来说是这样。我必须自己琢磨。我想问的问题和 OP 一样,还有更多。我的问题是:这个
is_class_tester (void (U::*)(void))
是什么鬼,它在 SFINAE(替换失败不是错误)的上下文中如何工作?
简单来说,Boost 使用这个结构体如下:
template <typename U>
char is_class_tester (void (U::*)(void));
template <typename U>
TypeBiggerThanChar is_class_tester (...);
template <typename T>
struct IsClass {
static const bool value = sizeof (is_class_tester<T>(0)) == 1;
};
一些观察:
这些函数模板并不是真正的函数模板,它们只是一对重载函数模板的前向声明。函数模板本身从来没有被定义。了解这一点并理解如何在不需要定义模板的情况下工作是理解这个结构的关键要素之一。
那些谈论如何使用第一个函数模板的答案都没抓住关键。因为其定义不存在,所以无法使用此函数模板。
请注意,由于奇怪的参数,只有当类型 T 是类时,两个函数模板中的第一个才有意义。基本类型和指针没有成员函数。第一个声明对于非类类型的语法是无效的。
相比之下,第二个重载函数模板适用于所有模板参数的语法,并且(如果存在)将接受任何参数,得益于其可变长参数...。(顺便说一句:这让我想起了我最喜欢的一行 C 程序,它可以解决世界上任何问题,只要用户输入格式正确。)
虽然函数模板声明不能用作函数,但这些声明可用于简单的编译时查询,例如有关返回类型的查询。这种查询不需要实际的定义,只需要原型。
这正是类模板 IsClass 用来定义编译时常量 IsClass<SomeType>::value 的方法。
那么,
IsClass<SomeType>::value
是如何获得其值的,以及它如何在编译时执行?编译器必须要么理解
sizeof (is_class_tester<T>(0))
,要么放弃尝试。我们需要根据
SomeType
是否为类来看两种情况。
情况1:
SomeType
是一个类。
在这里,两个模板声明都是有效的语法,因此编译器有两个可行的候选项可供选择。编译器可以并且必须选择第一个函数模板,因为选择规则规定可变参数函数在选择中具有最低优先级。所选函数返回一个char。由于sizeof(char)保证为1,因此在
SomeType
是类的情况下,
IsClass<SomeType>::value
将为true。
情况2:SomeType
不是一个类。
这就是SFINAE发挥作用的地方。第一个函数模板声明是无效的语法。编译器不能因为SFINAE而放弃在这里。它必须继续寻找替代方案,而第二个函数模板声明正好符合要求。唯一可行的函数返回一个TypeBiggerThanChar
,定义被省略了,但希望很明显。我们只想知道这个东西的sizeof()
。它比一个char大,所以如果SomeType
不是一个类,IsClass<SomeType>::value
将会是false。
U::*
表示指针所指向的函数是U
的成员,这是必要的,因为在 C++ 中,在成员函数的底层,第一个参数是指向调用它的对象的指针。 - Austin Hyde