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类型  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/536147.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用Phpstorm实现远程开发

Phpstorm除了能直接打开本地文件之外&#xff0c;还可以连接FTP&#xff0c;除了完成正常的数据传递任务之外&#xff0c;还可以进行本地文件与服务端文件的异同比较&#xff0c;同一文件自动匹配目录上传&#xff0c;下载&#xff0c;这些功能是平常IDE&#xff0c;FTP软件中少…

什么是递归函数?

文章目录递归函数递归例题特点效率优点递归函数 递归 递归就是一个函数在它的函数体内调用它自身。执行递归函数将反复调用其自身&#xff0c;每调用一次就进入新的一层。递归函数必须有结束条件。 当函数在一直递推&#xff0c;直到遇到墙后返回&#xff0c;这个墙就是结束条…

apache ab压力测试报错

今天用apache 自带的ab工具测试&#xff0c;当并发量达到1000多的时候报错如下&#xff1a; [rootaa~]# This is ApacheBench, Version 2.3 <Revision:655654> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Sof…

ngOnInit与constructor的区别

前世今生 Angular会管理一个组件的生命周期,包括组件的创建、渲染、子组件创建与渲染、当数据绑定属性变化时的校验、DOM移除之前毁销。 Angular提供组件生命周期钩子便于我们在某些关键点发生时添加操作。 组件生命周期钩子 指令和组件实例有个生命周期用于创建、更新和销…

Nginx配置性能优化

大多数的Nginx安装指南告诉你如下基础知识——通过apt-get安装&#xff0c;修改这里或那里的几行配置&#xff0c;好了&#xff0c;你已经有了一个Web服务器了。而且&#xff0c;在大多数情况下&#xff0c;一个常规安装的nginx对你的网站来说已经能很好地工作了。然而&#xf…

Angular的@Output与@Input理解

@Output与@Input理解 Output和Input是两个装饰器,是Angular2专门用来实现跨组件通讯,双向绑定等操作所用的。 @Input Component本身是一种支持 nest 的结构,Child和Parent之间,如果Parent需要把数据传输给child并在child自己的页面中显示,则需要在Child的对应 directiv…

腾讯云CDN配置

第一步&#xff1a;先去领取腾讯云CDN免费包23333333 以下为正式步骤&#xff1a; 在这里体现大家&#xff0c;域名一定要备案&#xff0c;另外要明白域名如何解析 前边问题不大&#xff0c;一切跟着腾讯云的套路来即可&#xff0c;需要注意的是网上后优化的配置大家可以自行…

Promise.all的深入理解

异步之Promise Promise.all Promise.all接收的promise数组是按顺序执行的还是一起执行的&#xff0c;也就是说返回的结果是顺序固定的吗&#xff1f; 目前有两种答案&#xff1a; 应该是同步执行的&#xff0c;但是这样就有效率问题了&#xff0c;如果想改成异步执行怎么办…

wordpress后台无法登录问题

之前给自己的WordPress加了个标签云&#xff0c;今天登录的时候突然发现网站后台进不去了&#xff0c;无奈各种找材料&#xff0c;这算是皇天不负有心人&#xff0c;总算是给我找到了&#xff0c;现在做一下记录 登录不上的原因在于&#xff1a;wp-admin和wp-admin/是不同的&a…

深入理解Angular订阅者模式

深入理解Angular订阅者模式 如果正在读此篇文章的你学过java,c++等面向对象语言,知道两个模式观察者模式和订阅者模式,分别为:Observer pattern,Pub-sub pattern(Subscriber) 接下来我们结合Angular来说明这两个模式。 Observer pattern This is a pattern of developme…

Ubuntu中安装python3

通过命令行安装Python3.*&#xff0c;只需要在终端中通过命令行安装即可&#xff1a; sudo apt-get install python3 Ubuntu的底层大多数采用的是Python2.*&#xff0c;Python3和Python2是互相不兼容的&#xff0c;完全没法通用的&#xff08;也不知道他们怎么想的o(TヘTo)&a…

Angular深入理解之指令

Angular深入理解之指令 指令有什么功能 Attribute directives 属性指令Structural directives 结构指令自定义属性指令自定义结构指令Angular深入理解之指令 对于初学Angular的同学来说,指令无疑是最痛苦的,那么我们怎么使用自定义的指令呢?指令到底怎么实现呢?为什么要写…

windows下Apache虚拟主机配置

找到host文件&#xff1a;C:\Windows\System32\drivers\etc\hosts 在hosts这么增加&#xff1a; 127.0.0.1 666.666.com 127.0.0.1 777.777.com 修改httpd.conf文件&#xff1a; 打开文件&#xff1a;xxx\xampp\apache\conf\httpd.conf 找到#LoadModule vhost_…

Angular深入理解基本组成

Angular深入理解基本组成 在讲指令时,我们先来了解一下Angular的基本概念和结构。 Module 模块 Angular 是模块化的.Modules 导出 classes, function, values , 以便在其他模块导入使用.angular应用由模块组成,每个模块都做此模块相关的事情组件、方法、类、服务等,他们都…

1607: 字符棱形

1607: 字符棱形 根据读入的字符和边长&#xff0c;勾画字符棱形。 Input 输入数据含有不超过50组的数据&#xff0c;每组数据包括一个可见字符c和一个整数n&#xff08;1≤n≤30&#xff09;。 Output 输出以c为填充字符&#xff0c;边长为n的棱形&#xff0c;勾画每个棱形…

Angular深入理解管道Pipe

Angular深入理解管道 纯管道与非纯管道区别的本质 Pure FunctionImpure Function内置Pipe pipe使用自定义Pipe 管道性能优化Angular深入理解管道 管道的链接 有学过linux shell的同学,应该知道管道,在shell中的管道是IPC,linux的进程间通讯有pipe,FIFO,signal。这里只是简单…

1959: 图案打印

1959: 图案打印 Description 一年一度的植树节就要到了&#xff0c;计算机学院学生准备在学院教学楼门前的空地上种植树木。为使树木排列得更加美观&#xff0c;大家决定把树木排列成菱形。现在告诉你我们所拥有的树木能排列成边长为N的菱形&#xff0c;请你编程输出树木所排…

JS事件的捕获和冒泡阶段

JS事件的捕获和冒泡阶段 这里介绍两个事件模型&#xff1a;IE事件模型与DOM事件模型 IE内核浏览器的事件模型是冒泡型事件&#xff08;没有捕获事件过程&#xff09;&#xff0c;事件句柄的触发顺序是从ChildNode到ParentNode。 <div id"ancestor"> <butt…

2016: C语言实验——打印金字塔

2016: C语言实验——打印金字塔 Description 输入n值&#xff0c;打印下列形状的金字塔&#xff0c;其中n代表金字塔的层数。 Input 输入只有一个正整数n。 Output 打印金字塔图形&#xff0c;其中每个数字之间有一个空格。 Sample Input 3 Sample Output 11 2 1 1 2 …

Anuglar中正确导入RxJS库

Anuglar中正确导入RxJS库 目前Angular2中的已经内建支持RxJS,所以我们在使用的时候可以直接导入使用了。 理解操作符导⼊ 在使用创建依赖于 RxJS 组件,服务,指令等等时, 你可能遇到处理运算符导⼊的问 题。 在项⽬中引⼊操作符最主要的⽅式像下⾯这样导⼊: import rxj…