C++学习---类型萃取---is_function

408 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第12天,点击查看活动详情

背景

定义在<type_traits>中,用于判断一个类型是否是函数类型,这里面包括std::function,lambda表达式,重载了调用运算符的类,注意函数指针并不是函数类型,属于基础的类型判断。

代码实现

gcc官网:gcc.gnu.org/

gcc代码下载:mirrors.tuna.tsinghua.edu.cn/help/gcc.gi…

gcc版本代码:gcc-7.5.0(branch分支)

文件位置:gcc/libstdc++-v3/include/std/type_traits

注意:以下的代码实现省略了一些宏定义部分,实际的代码还是参考gcc源码,这里仅用作相关原理分析。

实现分析

基础实现

继承false_type,默认返回false常量

  /// is_function
  template<typename>
    struct is_function
    : public false_type { };

常规函数识别

这里定义了返回值类型和参数类型,参数类型是0个或多个类型,这就是普通的函数类型。

  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes...) _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };

变参函数识别

这个识别与上一条的差异在于,特化参数时增加了一个`...`符号,这个是针对类似printf这样的函数的处理,表示可能有多个入参,前面的`_ArgTypes...`表示多个类型

  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes......) _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };

const和volatile限定词识别

const和volatile限定词进行组合有3种,再叠加前面两种,共6种,标识函数的类型。

实际上这样的函数定义多是成员函数,这时,const和volatile实际是作用到对象的this指针上,如类中的const修饰函数,说明this指针是const类型,不可修改类的成员变量。

  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes...) const _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes......) const _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };
    
  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes...) volatile _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };
    
  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes......) volatile _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };
    
  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes...) const volatile _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };
    
  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes......) const volatile & _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };

左值引用和右值引用识别

左值引用&和右值引用&&再进行叠加,总共有16种。

与const和volatile关键字不同,左值引用和右值引用不影响this指针,比如在右值引用修饰的函数中,`*this`返回的还是一个左值引用。

  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes...) & _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };
    
  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes......) & _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };
    
  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes...) const & _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };
    
  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes......) const & _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };
    
  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes...) volatile & _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };
    
  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes......) volatile & _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };
    
  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes...) const volatile & _GLIBCXX_NOEXCEPT_QUAL>                                                                       
    : public true_type { };   
     
  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes......) const volatile & _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };

// 右值引用
  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes...) && _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };
...
  template<typename _Res, typename... _ArgTypes _GLIBCXX_NOEXCEPT_PARM>
    struct is_function<_Res(_ArgTypes......) const volatile && _GLIBCXX_NOEXCEPT_QUAL>
    : public true_type { };

主要的区别在函数重载决议时,左值引用和右值引用修饰的函数表现不同:

  • 无引用修饰符:隐式对象参数调用时默认是左值引用,而且允许绑定右值到隐式对象上;
  • 左值引用:隐式对象参数调用调用时是左值引用;
  • 右值引用:隐式对象参数调用时是右值引用。

如下的例子中,A中只有一个无引用修饰函数,B中定义了两个同名函数f(),一个为左值引用,一个为右值引用。

#include <iostream>

struct A{
    void f(){std::cout << "no reference!!!\n";};
};

struct B{
    void f() & {std::cout << "left reference!!!\n";};
    void f() && {std::cout << "right reference!!!\n";};
};

int main(){
    std::cout << "------no split reference------\n";
    A a;
    a.f();
    std::move(a).f();
    A().f();

    std::cout << "------split reference------\n";
    B b;
    b.f();
    std::move(b).f();
    B().f();
    return 0;
}

执行结果如下:

没有区分时,都调用无引用修饰的函数;区分时,会根据调用的对象引用类型决定调用哪一个函数。

最新的实现方式

只需要判定一个类型不是const类型或者引用类型就可以确定该类型是函数类型,使用的是排除法。相比而言,上面的方法更适合初学者理解这个过程。

template<class T>
struct is_function : std::integral_constant<bool,
    !std::is_const<const T>::value && !std::is_reference<T>::value> {};

使用案例

#include <iostream>
#include <type_traits>
                  
void func_1(){};              
void (*func_point)();
           
struct A{                                                                                                                
    void func_2() const&&;                           
};                                                                
                                                                      
template<typename>                                                      
struct Test{};                                                                      
                                                                                        
template<typename T, typename U>                                                          
struct Test<U T::*>{                                                                                                     
    using member_type = U;
};                              
                    
int main(){               
    std::cout << std::boolalpha;
    std::cout << "void(void):" << std::is_function<void(void)>::value << std::endl;
    std::cout << "func_1:" << std::is_function<decltype(func_1)>::value << std::endl;
    std::cout << "func pointer:" << std::is_function<decltype(func_point)>::value << std::endl;
    std::cout << "func pointer is pointer:" << std::is_pointer<decltype(func_point)>::value << std::endl;
    using T = Test<decltype(&A::func_2)>::member_type; // T is void() const&&        
    std::cout << "member function:" << std::is_function<T>::value << std::endl;                
                                                                                                                         
    return 0;                                                                                                                                            
}

从结果不难看出:

  • 上面列出的普通函数,std::function,lambda函数,成员函数都是函数类型;
  • 注意函数指针并不是函数类型,而是指针类型。

总结

判断一个类型是否是函数类型,这里面包括std::function,lambda表达式,重载了调用运算符的类,注意函数指针并不是函数类型。其中const修饰符,volatile修饰符,左值右值引用都要考虑到其特殊作用。