C++类型萃取之type_traits和type_info
2018-02-11 17:07
579 查看
类型萃取
类型判断
typeid
decltype和declval
enable_if
头文件 #include
type_trits的类型选择功能,在一定程度上可以消除冗长的switch-case或者if-else的语句,降低程序的复杂程度。
这些类型判断的方法从std::integral_constant派生,用来检查模板类型是否为某种类型,通过这些trait可以获取编译期检查的bool值结果。
下面的表格是一些常用的判断类型traits。更过从网址 点击获取。
使用方法:
判断类型的traits一般和std::enable_if结合起来使用,通过SFINAE特性来实现功能更强大的重载。后面会讲到。
判断两个类型之间的关系traits
简单介绍一下is_same的用法:
类型的转换traits
常用的类型转换traits包括对const的修改—-const的移除和添加,引用的修改—–引用的移除和添加,数组的修改和指针的修改。
下表为类型转换的方法:
简单介绍一下使用方法:
具体可以参考c++11深入理解93页。
在讲解typeid神秘面纱之前,我们先了解一下,RTTI(Run-Time Type Identification),中文为运行时类型识别,它使程序能够获取由基指针或引用所指向的对象的实际派生类型。即允许 “用指向基类的指针或引用来操作对象” 的程序能够获取到 “这些指针或引用所指对象” 的实际派生类型。
在C++中,为了支持RTTI提供了两个操作符:dynamic_cast和typeid。
dynamic_cast允许运行时刻进行类型转换,从而使程序能够在一个类层次结构中安全地转化类型,与之相对应的还有一个非安全的转换操作符static_cast,因为这不是本文的讨论重点,所以这里不再详述,感兴趣的可以自行查阅资料。
typeid是C++的关键字之一,等同于sizeof这类的操作符。typeid操作符的返回结果是名为type_info的标准库类型的对象的引用。
我们来看一下如何使用:
上面是在gcc编译上编译的,结果与vc++,clang都大不相同。
比如下面代码:
由于函数的入参都是两个模板参数,导致我们不能直接确定返回类型,那么我们可以通过decltype来推断函数返回类型。
上面的比较繁琐,所以我们可以使用返回类型后置去简化。
这样看起来就舒服多了。
但是有些时候我们不能通过decltype来获取类型了,如下面:
上面的代码将会编译报错,因为A没有默认构造函数,对于这种没有默认构造函数的类型,我们如果希望能推导其成员函数的返回类型,则需要借助std::declval。
修改为:
上面的代码可以通过,因为std::declval能够获取任何类型的临时值,不管它有没有默认构造函数。因为我们通过declval()获取了A的临时对象。需要注意一点,declval获取的临时值不能用于求值,因此必须使用decltype来推断出最终的返回类型。
其实上面做了这么多,还是比较麻烦,C++11提供了另外一个trait——std::result_of,用来在编译期获取一个可调用对象的返回类型。
上面的代码改写如下:
这段代码实际上等价于 decltype( std::declval()(std::declval()))。
我们通过一个例子来了解一下SFINAE机制:
上面运行的时候,将会匹配到第二个重载函数,在匹配的过程中,当匹配到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的原型如下:
简单介绍一下使用的方法:
上面的函数模板通过enable_if做了限定,只能接受整型和浮点型,我们来看一下foo(1)运行步骤:
类型判断
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 <iostream> #include <type_traits> 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; | 判断前面的模板参数类型能否转换为后面的模板参数类型 |
#include <iostream> #include <type_traits> 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; | 移除引 4000 用 |
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 <iostream> #include <type_traits> 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 <typeinfo> 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 <typename F, typename Arg> 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 <type_traits> 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<A>()(std::declval<int>())) i = 4;
上面的代码可以通过,因为std::declval能够获取任何类型的临时值,不管它有没有默认构造函数。因为我们通过declval()获取了A的临时对象。需要注意一点,declval获取的临时值不能用于求值,因此必须使用decltype来推断出最终的返回类型。
其实上面做了这么多,还是比较麻烦,C++11提供了另外一个trait——std::result_of,用来在编译期获取一个可调用对象的返回类型。
上面的代码改写如下:
std::result_of<A(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<T>::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<T>::value变为std::is_arithmetic<int>::value,此时返回值为true 3. std::enable_if<std::is_arithmetic<T>::value, T>中的最后一个T为int,通过::type获取类型。 4. foo函数的返回值被确定为int类型
相关文章推荐
- C++通过typeinfo获取对象类型.cpp
- 【旧资料整理】C++ 用typeinfo头文件输出变量类型
- C++ 模板类型萃取技术 traits
- c++:数据类型的判断type_traits
- C++模板中type_traits(类型特化)
- c\c++复习基础要点15----c++运行时类型识别 dynamic_cast typeid type_info
- c++11——type_traits 类型萃取
- 第17课 类型萃取(1)_基本的type_traits
- C++ 学习笔记(19)new/delete表达式、定位new、typeid、dynamic_cast、type_info、枚举类型、成员函数指针、union、位域、volatile限定符、链接指示
- 【STL】类型萃取(TypeTraits)
- C++ - RTTI(RunTime Type Information)运行时类型信息 详解
- 关于C++类型萃取
- c++模拟MFC中运行时类型识别(RTTI)(Run-Time Type Identification)
- 将任意类型映射到一个唯一整数(C++模板实现TypeList)
- INFO: OleDbType 枚举与 Microsoft Access 数据类型
- 【转】C++之traits(萃取技术)
- C++的类型萃取技术
- Type conversions in C++类型转换
- C++类型萃取
- C++运行时获取类型信息的type_info类与bad_typeid异常