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
,但是第二个是一个指向T
types成员的指针。 这里的int
部分并不重要,即。 这甚至可以用于没有int
types成员的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_tag
types作为参数,并返回对不同唯一大小的char数组的引用。
标签在is_function
中is_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。