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 <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; |
判断前面的模板参数类型能否转换为后面的模板参数类型 |
简单介绍一下is_same的用法:
#include <iostream>
#include <type_traits>int main()
{std::cout << "int: " << std::is_same<int, int>::value << std::endl;//这里使用了decltype可以获取变量的类型为intstd::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 <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; //输出结果为3return 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++类型萃取之type_traits和type_info相关推荐
- type_traits 类型萃取
一. c++ traits traits是c++模板编程中使用的一种技术,主要功能: 把功能相同而参数不同的函数抽象出来,通过traits将不同的参数的相同属性提取出来,在函数中利用这些用traits ...
- C/Cpp / STL / 类型萃取
作用 类型萃取使用模板技术来萃取类型(包含自定义类型和内置类型)的某些特性,用以判断该类型是否含有某些特性,从而在泛型算法中来对该类型进行特殊的处理用来达到提高效率或者其他的目的. 类型萃取的实现的基 ...
- C++的类型萃取技术
http://www.cppblog.com/nacci/archive/2005/11/03/911.aspx?spm=0.0.0.0.iyJqvt&file=911.aspx 自从C++中 ...
- C++ 模板类型萃取技术 traits
当函数,类或者一些封装的通用算法中的某些部分会因为数据类型不同而导致处理或逻辑不同(而我们又不希望因为数据类型的差异而修改算法本身的封装时),traits会是一种很好的解决方案.(类型测试发生在编译期 ...
- C++特化的应用——类型萃取
提出问题:如何实现一个对于拷贝内置类型和自定义类型通用的拷贝函数? 1.拷贝内置类型 对于内置类型我们可以用memcpy进行拷贝,因为memcpy属于浅拷贝,内置类型不涉及资源管理的问题. 2.拷贝自 ...
- C++之类型萃取技巧
使用类型萃取的原因 就是当你的顺序表是自定义类型,我们进行顺序表增容的时候,这个时候会出现一个问题,比如string类型,这个类型中有一个_buf与_ptr,当储存少于16个的时候这时会储存在_buf ...
- [c++]——什么是类型萃取
类型萃取 类型萃取从字面意思上来说其实就是帮助我们挑选某个对象的类型,筛选特定的对象来做特定的事. C++中的类型萃取并不是每个人都熟知,他们一般都出现在STL库底层的实现原理中,和笔者一样,相信听到 ...
- C++ — 类型萃取
类型萃取 在编程中我们可能时常会听到类型萃取这个高大上的"学术名词",我们今天来探究一下这个高大上的学术名词 到底是何方神圣,先看看官方的解释类型萃取使用模板技术来萃取类型(包含自 ...
- C++模板编程之类型萃取 惊鸿一瞥
一.从模板函数std::distance(计算迭代器的距离)开始 #include <iostream> #include <vector> #include <list ...
最新文章
- master中的系统目录与用户数据库中的区别
- 公式编辑语言:LaTeX/Advanced Mathematics
- Redis 4.x/5.x未授权访问漏洞
- 【Linux】一步一步学Linux——sudo配置文件详解(106)
- 史上最全搞怪WC标志(组图)--设计者太有才了。
- 自动化测试特定区域滑动_自动化用户特定实体的访问控制
- Toastr.js插件用法
- WP8.1学习系列(第二章)——Toast通知
- html5中的web worker的用法
- Linux DRM KMS 驱动简介
- Unity 粒子特效 之 LogoEffect ParticleSystem 文字图片logo粒子特效
- 睡眠麻痹 CSP HSP
- 润乾报表 echarts统计图分类显示不全
- 《信息物理融合系统(CPS)设计、建模与仿真——基于 Ptolemy II 平台》——1.4 角色模型...
- 七夕送什么礼物会让对方惊喜呢!2022最全情人节礼物指南
- 最好用的xshell替代软件----FinalShell工具
- APP——功耗测试(耗电测试)——adb命令复杂获取分析
- gitblit中忘记admin密码,怎么办?附详细解决方法和截图
- 如何用Python找出英语和汉语中特定词性的单词
- 惠普电脑如何重装Linux系统,如何把惠普下的Linux操作系统换为windows 7
热门文章
- Python for循环 - Python零基础入门教程
- 用c语言编译二叉树,C语言 数据结构平衡二叉树实例详解
- 串口服务器信号连接不上,使用RS485串口服务器经常遇到的问题
- linux 查看进程的信号,Linux 进程信号查看与控制
- mysql索引 物理文件_MySQL体系结构之物理文件
- 总线制和多线制示意图_主机总线线 总线制和多线制示意图
- linux 格式化 目录,Linux 磁盘分区、格式化、目录挂载
- mysql sum id 5_mysql怎么使用sum()求id字段的和?
- fullcalendar php,日历插件fullcalendar+php的使用教程 — 读取json数据
- 不采取任何措施 盒盖_得了癌症如果不化疗能活多久?医生的答案很实在