Eric Niebler如何实现std :: is_function?

Com*_*sMS 54 c++ templates type-traits c++11 c++14

上周,埃里克Niebler 啾啾了一个非常紧凑的实现std::is_functiontraits类:

#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>
{};
Run Code Online (Sandbox Code Playgroud)

但它是如何工作的?

Com*_*sMS 51

一般的想法

而不是列出所有有效的函数类型,例如cpprefereence.com上示例实现,此实现列出了所有不是函数的类型,然后只有true在没有匹配的情况下才会解析.

非功能类型列表包括(从下到上):

  • 类和联合(包括抽象类型)
  • 可以从函数返回的任何内容(包括void和引用类型)
  • 数组类型

与任何非函数类型都不匹配的类型是函数类型.请注意,std::is_function显式地将可调用类型(如lambda)或具有函数调用运算符的类视为不是函数.

is_function_impl_

我们is_function_impl为每种可能的非函数类型提供了一个函数重载.函数声明可能有点难以解析,所以让我们将其分解为类和联合案例:

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

该行声明了一个函数模板is_function_impl_,它接受一个类型的参数,priority_tag<3>并返回对4 chars 数组的引用.按照C的古代惯例,声明语法因阵列类型的存在而变得非常复杂.

此函数模板采用两个模板参数.第一个是无约束的T,但第二个是指向T类型成员的指针int.int这里的部分并不重要,即.这甚至适用于T没有任何类型成员的s int.它的作用是它会导致Ts不是类或联合类型的语法错误.对于其他类型,尝试实例化函数模板将导致替换失败.

类似的技巧用于priority_tag<2>priority_tag<1>重载,它们使用第二个模板参数来形成表达式,这些表达式仅分别编译为T有效函数返回类型或数组类型.只有priority_tag<0>重载没有这样的约束第二模板参数,因此可以用any实例化T.

总而言之,我们声明了四个不同的重载is_function_impl_,它们的输入参数和返回类型不同.它们中的每一个都使用不同的priority_tag类型作为参数,并返回对不同唯一大小的char数组的引用.

标签调度 is_function

现在,在实例化时is_function,它实例化is_function_implT.请注意,由于我们为此函数提供了四种不同的重载,因此必须在此处进行重载解析.由于所有这些重载都是功能模板,这意味着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 在实施中发现了一个错误.不完整类类型的数组将被错误地分类为函数.这是因为数组类型的当前检测机制不适用于不完整类型.

  • 它将0转换为`T*`,取消引用它,然后索引到结果引用.这只有在`T`是可以索引的类型(如数组)时才有效.它可以替换为`std :: declval <T>()[0]`,但这将是更多的字符.:-) (3认同)
  • 请考虑将我的测试用例添加到您的答案中,作为算法失败的示例:http://coliru.stacked-crooked.com/a/b655c530b381ec48 (3认同)
  • @Arunmu写道:_"或者使用'的std :: is_array`数组专业化或者是有一些魔法`is_array`会错过?" _为什么实例化一个模板时,你不就得了? (2认同)