上周,Eric Niebler tweeted为std::is_function
特征类提供了一个非常紧凑的实现:
#include <type_traits>
template<int I> struct priority_tag : priority_tag<I - 1> {};
template<> struct priority_tag<0> {};
// Function types here:
template<typename T>
char(&is_function_impl_(priority_tag<0>))[1];
// Array types here:
template<typename T, typename = decltype((*(T*)0)[0])>
char(&is_function_impl_(priority_tag<1>))[2];
// Anything that can be returned from a function here (including
// void and reference types):
template<typename T, typename = T(*)()>
char(&is_function_impl_(priority_tag<2>))[3];
// Classes and unions (including abstract types) here:
template<typename T, typename = int T::*>
char(&is_function_impl_(priority_tag<3>))[4];
template <typename T>
struct is_function
: std::integral_constant<bool, sizeof(is_function_impl_<T>(priority_tag<3>{})) == 1>
{};
但它是如何运作的?
答案 0 :(得分:51)
此实现不是列出所有有效的函数类型,例如sample implementation over on cpprefereence.com,而是列出所有不函数的类型,然后只解析为true
这些都不匹配。
非功能类型列表包括(从下到上):
void
和引用类型)与任何非函数类型都不匹配的类型是函数类型。请注意,std::is_function
明确地将可调用类型(如lambdas)或具有函数调用运算符的类视为而不是是函数。
is_function_impl_
我们为每个可能的非函数类型提供is_function_impl
函数的一个重载。函数声明可能有点难以解析,所以让我们将其分解为类和联合案例的例子:
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
成员的指针。这里的int
部分并不重要,即。这甚至适用于没有任何T
类型成员的int
。它的作用是它会导致T
的语法错误,这些错误不属于类或联合类型。对于其他类型,尝试实例化函数模板将导致替换失败。
类似的技巧用于priority_tag<2>
和priority_tag<1>
重载,它们使用第二个模板参数来形成仅为T
编译的表达式,分别是有效函数返回类型或数组类型。只有priority_tag<0>
重载没有这样的约束第二个模板参数,因此可以使用任何T
进行实例化。
总而言之,我们为is_function_impl_
声明了四个不同的重载,它们的输入参数和返回类型不同。它们中的每一个都使用不同的priority_tag
类型作为参数,并返回对不同唯一大小的char数组的引用。
is_function
现在,在实例化is_function
时,它会使用is_function_impl
实例化T
。请注意,由于我们为此函数提供了四种不同的重载,因此必须在此处进行重载解析。由于所有这些重载都是函数 templates ,这意味着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>
。
Johannes Schaub found a bug在实施中。不完整类类型的数组将被错误地分类为函数。这是因为数组类型的当前检测机制不适用于不完整类型。