Eric Niebler如何实现std :: is_function?

时间:2017-04-18 11:20:51

标签: c++ c++11 templates c++14 typetraits

上周,Eric Niebler tweetedstd::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>
{};

但它是如何运作的?

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在实施中。不完整类类型的数组将被错误地分类为函数。这是因为数组类型的当前检测机制不适用于不完整类型。