总体思路
而不是列出所有有效的函数类型,例如cpprefereence.com 上的示例实现 http://en.cppreference.com/w/cpp/types/is_function,此实现列出了所有类型not函数,然后只解析为true
如果这些都不匹配。
非函数类型列表包括(从下到上):
- 类和联合(包括抽象类型)
- 可以从函数返回的任何内容(包括
void
和参考类型)
- 数组类型
与任何这些非函数类型都不匹配的类型是函数类型。注意std::is_function
显式地将可调用类型(如 lambda 或带有函数调用运算符的类)视为not是函数。
is_function_impl_
我们提供了一种超载is_function_impl
每个可能的非函数类型的函数。函数声明可能有点难以解析,所以让我们以以下示例来分解它:阶级和工会 case:
template<typename T, typename = int T::*>
char(&is_function_impl_(priority_tag<3>))[4];
这一行声明了一个函数模板is_function_impl_
需要一个类型参数priority_tag<3>
并返回对 4 数组的引用char
s。正如 C 语言自古以来的惯例,声明语法因数组类型的存在而变得极其复杂。
该函数模板采用两个模板参数。第一个只是无约束T
,但第二个是指向成员的指针T
类型的int
. The int
这里的部分并不重要,即。这甚至适用于T
没有任何类型的成员int
。但它的作用是会导致语法错误T
不属于类或联合类型。对于那些其他类型,尝试实例化函数模板将导致替换失败。
类似的技巧也用于priority_tag<2>
and priority_tag<1>
重载,它使用第二个模板参数来形成仅编译的表达式T
s 分别是有效的函数返回类型或数组类型。只有priority_tag<0>
重载没有这样的约束第二个模板参数,因此可以用任何实例化T
.
总而言之,我们声明了四种不同的重载is_function_impl_
,它们的输入参数和返回类型有所不同。他们每个人都采取不同的priority_tag
类型作为参数并返回对不同唯一大小的 char 数组的引用。
标签调度在is_function
现在,在实例化时is_function
,它实例化is_function_impl
with T
。请注意,由于我们为此函数提供了四种不同的重载,因此必须在此处进行重载解析。由于所有这些重载都是函数模板,这意味着SFINAE http://en.cppreference.com/w/cpp/language/sfinae有机会介入。
因此,对于函数(并且仅是函数),所有重载都会失败,除了最常见的重载priority_tag<0>
。那么,如果实例化是最通用的重载,为什么实例化并不总是解决该重载呢?因为我们重载函数的输入参数。
注意priority_tag
是这样构造的priority_tag<N+1>
公开继承自priority_tag<N>
。现在,自从is_function_impl
在这里被调用priority_tag<3>
,该过载是更好的匹配比其他重载决议,所以它会首先尝试。仅当由于替换错误而失败时,才会尝试下一个最佳匹配,即priority_tag<2>
超载。我们继续以这种方式,直到找到可以实例化的重载或者达到priority_tag<0>
,它不受限制并且始终有效。由于所有非函数类型都被较高的 prio 重载覆盖,因此这种情况只能发生在函数类型上。
评估结果
我们现在检查调用返回的类型的大小is_function_impl_
来评估结果。请记住,每个重载都会返回对不同大小的 char 数组的引用。因此我们可以使用sizeof
检查选择了哪个重载并仅将结果设置为true
如果我们到达priority_tag<0>
超载。
已知错误
约翰内斯·绍布发现一个错误 https://stackoverflow.com/questions/43470741/how-does-eric-nieblers-implementation-of-stdis-function-work#comment74188378_43470962在实施中。不完整类类型的数组将被错误地分类为函数。这是因为当前数组类型的检测机制不适用于不完整的类型。