Eric Niebler如何实现std :: is_function?

上周,Eric Niebler为std::is_function traits类推送了一个非常紧凑的实现:

 #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> {}; 

但是它是如何工作的?

总的想法

这个实现并没有列出所有有效的函数types,比如cpprefereence.com上的示例实现 ,而是列出了所有不是函数的types,只有在没有匹配时才parsing为true

非函数types列表由(从下到上)组成:

  • 类和工会(包括抽象types)
  • 任何可以从函数返回的东西(包括void和引用types)
  • 数组types

不匹配任何非函数types的types是函数types。 请注意, std::is_function明确地认为可调用的types,如lambdas或具有函数调用操作符的类不是函数。

is_function_impl_

我们为每个可能的非函数types提供了is_function_impl函数的一个重载。 函数声明可能有点难于parsing,所以让我们分解一下类和联合的例子:

 template<typename T, typename = int T::*> char(&is_function_impl_(priority_tag<3>))[4]; 

这一行声明一个函数模板is_function_impl_ ,它接受一个types为priority_tag<3>参数,并返回一个由4个char的数组的引用。 按照古代C语言惯例,声明语法由于数组types的存在而变得非常复杂。

这个函数模板有两个模板参数。 第一个是一个不受约束的T ,但是第二个是一个指向Ttypes成员的指针。 这里的int部分并不重要,即。 这甚至可以用于没有inttypes成员的T 它所做的是,它将导致不是类或联合types的T的语法错误。 对于其他types,试图实例化函数模板将导致replace失败。

类似的技巧用于priority_tag<2>priority_tag<1>重载,它们使用它们的第二个模板参数来形成expression式,这些expression式只能分别针对T s是有效函数返回types或数组types进行编译。 只有priority_tag<0>过载不具有这样的约束第二模板参数,因此可以用任何T实例化。

总而言之,我们为is_function_impl_声明了四个不同的重载,它们的input参数和返回types有所不同。 它们中的每一个都采用不同的priority_tagtypes作为参数,并返回对不同唯一大小的char数组的引用。

标签在is_functionis_function

现在,当实例化is_function ,它用T来实例化is_function_impl 。 请注意,由于我们为此function提供了四种不同的重载,因此必须在这里进行重载parsing。 而且由于所有这些重载都是function模板 ,这意味着SFINAE有机会进入。

因此,对于函数(只有函数),除了最常用的priority_tag<0>之外,所有的重载都会失败。 那么为什么不实例化总是解决这个过载,如果它是最普通的呢? 由于我们的重载函数的input参数。

注意, priority_tag是以这样的方式构build的,即priority_tag<N+1>priority_tag<N>公开inheritance。 现在,因为is_function_impl在这里被调用priority_tag<3> ,所以重载比其他重载parsing更好的匹配 ,所以它将首先被尝试。 只有在由于replace错误而失败的情况下,才会尝试次最佳匹配,即priority_tag<2>过载。 我们以这种方式继续下去,直到find一个可以被实例化的重载,或者我们达到priority_tag<0> ,这个约束并没有被约束,并且一直工作。 由于所有的非函数types都被更高级的重载覆盖,所以这只能发生在函数types上。

评估结果

我们现在检查调用is_function_impl_返回的types的大小来评估结果。 请记住,每个重载都会返回对不同大小的char数组的引用。 因此,我们可以使用sizeof来检查select了哪个超载,并且只有当我们到达priority_tag<0>过载时才将结果设置为true

已知的错误

Johannes Schaub在执行中发现了一个错误 。 一个不完整的types数组将被错误地分类为一个函数。 这是因为数组types的当前检测机制不适用于不完整的types。