C++类型萃取之type_traits和type_info

  • 类型萃取
    • 类型判断
    • typeid
    • decltype和declval
    • enable_if

类型萃取

通过type_traits可以实现在编译期计算、查询、判断、转换和选择,增强了泛型编程的能力,也增强了我们程序的弹性,让我们能够在编译期就能够优化改进甚至排错,进一步提高代码质量。

头文件 #include

类型判断

type_trits提供了丰富的编译期计算、查询、判断、转换和选择的帮助类,在很多场合中会使用到这些特性。

type_trits的类型选择功能,在一定程度上可以消除冗长的switch-case或者if-else的语句,降低程序的复杂程度。

这些类型判断的方法从std::integral_constant派生,用来检查模板类型是否为某种类型,通过这些trait可以获取编译期检查的bool值结果。

下面的表格是一些常用的判断类型traits。更过从网址 点击获取。

traits类型 说明
template
struct is_void;
T是否为void类型
template
struct is_floating_point;
T是否为浮点类型
template
struct is_array;
T是否为数组类型
template
struct is_pointer;
T是否为指针类型(包括函数指针,但不包括成员(函数)指针)
template
struct is_enum;
T是否为枚举类型
template
struct is_union;
T是否为非union的class/struct类型
template
struct is_class;
T是否为类类型而不是union类型
template
struct is_funtion;
T是否为函数类型
template
struct is_reference;
T是否为引用类型(左值引用或者右值引用)
template
struct is_arithmetic;
T是否为整型和浮点类型
template
struct is_fundamental;
T是否为整型、浮点、void、或nullptr_t类型
template
struct is_object;
T是否为一个对象类型(不是函数、不是引用、不是void)
template
struct is_scalar;
T是否为arithmetic、enumeration、pointer、pointer to member或std::nullptr_t类型
template
struct is_compound;
T是否非fundamental类型构造的
template
struct is_member_pointer;
T是否为成员函数指针类型
template
struct is_polymorphic;
T是否有虚函数
template
struct is_abstract;
T是否为抽象类
template
struct is_signed;
T是否是有符号类型
template
struct is_unsigned;
T是否是无符号类型
template
struct is_const;
T是否为const修饰的类型

使用方法:

#include 
#include 

int main()
{
    std::cout << "is_const:" << std::endl;
    std::cout << "int: " << std::is_const<int>::value << std::endl;
    std::cout << "const int: " << std::is_const<const int>::value << std::endl;
    return 0;
}

输出结果为:  

is_const:
int: 0
const int: 1

判断类型的traits一般和std::enable_if结合起来使用,通过SFINAE特性来实现功能更强大的重载。后面会讲到。

判断两个类型之间的关系traits

traits 说明
template
struct is_same;
判断两个类型是否相同
template
struct is_base_of;
判断Base类型是否为Derived类型的基类
template
struct is_convertible;
判断前面的模板参数类型能否转换为后面的模板参数类型

简单介绍一下is_same的用法:

#include 
#include 

int main()
{
    std::cout << "int: " << std::is_same<int, int>::value << std::endl;
    //这里使用了decltype可以获取变量的类型为int
    std::cout << "int: " << std::is_same<decltype(a), int>::value << std::endl;
    std::cout << "const int: " << std::is_same<int, unsigned int>::value << std::endl;
    return 0;
}

输出结果为:
int: 1
int: 1
const int: 0

类型的转换traits

常用的类型转换traits包括对const的修改—-const的移除和添加,引用的修改—–引用的移除和添加,数组的修改和指针的修改。

下表为类型转换的方法:

traits 说明
template
struct remove_const;
移除const
template
struct add_const;
添加const
template
struct remove_reference;
移除引用
template
struct add_lvalue_reference;
添加左值引用
template
struct add_rvalue_reference;
添加右值引用
template
struct remove_extents;
移除数组顶层的维度
template
struct remove_all_extents;
移除数组所有的维度
template
struct remove_pointer;
移除指针
template
struct add_pointer;
添加指针
template
struct decay;
移除cv或添加指针
template
struct common_type;
获取公共类型

简单介绍一下使用方法:

具体可以参考c++11深入理解93页。

#include 
#include 

int main()
{
    std::cout << "int: " << std::is_same<int, add_const<int>>::value << std::endl;
    return 0;
}

输出结果为:
int: 0

typeid

包含头文件 #include

在讲解typeid神秘面纱之前,我们先了解一下,RTTI(Run-Time Type Identification),中文为运行时类型识别,它使程序能够获取由基指针或引用所指向的对象的实际派生类型。即允许 “用指向基类的指针或引用来操作对象” 的程序能够获取到 “这些指针或引用所指对象” 的实际派生类型。

在C++中,为了支持RTTI提供了两个操作符:dynamic_cast和typeid。

  • dynamic_cast允许运行时刻进行类型转换,从而使程序能够在一个类层次结构中安全地转化类型,与之相对应的还有一个非安全的转换操作符static_cast,因为这不是本文的讨论重点,所以这里不再详述,感兴趣的可以自行查阅资料。
  • typeid是C++的关键字之一,等同于sizeof这类的操作符。typeid操作符的返回结果是名为type_info的标准库类型的对象的引用。

我们来看一下如何使用:

#include 

struct Base { virtual ~Base() = default; };
struct Derived : Base {};

int main()
{
    Base b1;
    Derived d1;

    const Base *pb = &b1;
    std::cout << typeid(*pb).name() << '\n';
    pb = &d1;
    std::cout << typeid(*pb).name() << '\n';
    std::cout << typeid(1).name() << '\n';
    std::cout << typeid(2.444).name() << '\n';

    return 0;
}

输出结果:
4Base
7Derived
i
d

上面是在gcc编译上编译的,结果与vc++,clang都大不相同。

decltype和declval

有时候要获取函数的返回类型是一件比较困难的事情:
比如下面代码:

template <typename F, typename Arg>
?? func(F f, Arg arg)
{
    return f * arg;
}

由于函数的入参都是两个模板参数,导致我们不能直接确定返回类型,那么我们可以通过decltype来推断函数返回类型。

template 
decltype((*(F*)0)*((*(Arg*)0))) func(F f, Arg arg)
{
    return f * arg;
}

上面的比较繁琐,所以我们可以使用返回类型后置去简化。

template <typename F, typename Arg>
auto func(F f, Arg arg)->decltype(f * arg )
{
    return f * arg;
}

这样看起来就舒服多了。

但是有些时候我们不能通过decltype来获取类型了,如下面:

#include 

class A
{
    A()=delete;
public:
    int operator() ( int i )
    {
        return i;
    }
};

int main()
{
    int a = A()(3);
    decltype( A()(0) ) i = 4;
    std::cout << i << std::endl;
    std::cout << a << std::endl; //输出结果为3
    return 0;
}

上面的代码将会编译报错,因为A没有默认构造函数,对于这种没有默认构造函数的类型,我们如果希望能推导其成员函数的返回类型,则需要借助std::declval。

修改为:

decltype( std::declval()(std::declval())) i = 4;

上面的代码可以通过,因为std::declval能够获取任何类型的临时值,不管它有没有默认构造函数。因为我们通过declval()获取了A的临时对象。需要注意一点,declval获取的临时值不能用于求值,因此必须使用decltype来推断出最终的返回类型。

其实上面做了这么多,还是比较麻烦,C++11提供了另外一个trait——std::result_of,用来在编译期获取一个可调用对象的返回类型。

上面的代码改写如下:

std::result_of(int)>::type i = 4;

这段代码实际上等价于 decltype( std::declval()(std::declval()))。

enable_if

在讲enable_if之前我们先来了解什么是SFINAE,它是Substitution failure is not an error 的首字母缩写。

我们通过一个例子来了解一下SFINAE机制:

template<typename T>
void Fun(T *t)
{
    *t *+= 1;
}

template<typename T>
void Fun(T t)
{
    t += 1;
}

int main()
{
    Fun(1);

    return 0;
}

上面运行的时候,将会匹配到第二个重载函数,在匹配的过程中,当匹配到void Fun(T t)时,将一个非0的整数来替换T 是错误的,此时编译器并不会报错,此时就叫failure,然后继续匹配其他的重载函数,如果最后发现void Fun(T t)能匹配上,整个过程就不会报错,如果匹配不到就会报error,这就是为什么叫Substitution failure is not an error。

这个规则就叫SFINAE

std::enable_if利用SFINAE实现根据条件选择重载函数,std::enable_if的原型如下:

template<bool B, class T = void>
struct enable_if;

简单介绍一下使用的方法

//is_arithmetic为判断是否为整型和浮点类型的traits
//这里在使用的时候需要加上typename在enable_if前面
//是要告诉编译器后面的标识符是一个类型名来处理,否则会被编译器当做静态变量处理
template<class T>
typename std::enable_if<std::is_arithmetic::value, T>::type foo(T t)
{
    return t;
}

int main()
{
    auto r = foo(1);
    auto r1 = foo(1.2);

    std::cout << r <<std::endl; //1,整数
    std::cout << r1 <<std::endl; //1.2,浮点数
    //auto r2 = foo("test"); //编译错误
    return 0;
}

上面的函数模板通过enable_if做了限定,只能接受整型和浮点型,我们来看一下foo(1)运行步骤:

1. 根据传入的实参1,推断出T为int类型
2. std::is_arithmetic::value变为std::is_arithmetic::value,此时返回值为true
3. std::enable_if::value, T>中的最后一个T为int,通过::type获取类型。
4. foo函数的返回值被确定为int类型  

你可能感兴趣的:(C++)