通过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
包含头文件 #include
在讲解typeid神秘面纱之前,我们先了解一下,RTTI(Run-Time Type Identification),中文为运行时类型识别,它使程序能够获取由基指针或引用所指向的对象的实际派生类型。即允许 “用指向基类的指针或引用来操作对象” 的程序能够获取到 “这些指针或引用所指对象” 的实际派生类型。
在C++中,为了支持RTTI提供了两个操作符:dynamic_cast和typeid。
我们来看一下如何使用:
#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都大不相同。
有时候要获取函数的返回类型是一件比较困难的事情:
比如下面代码:
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之前我们先来了解什么是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类型