深入解析decltype和decltype(auto)
decltype关键字是C++11新标准引入的关键字,它和关键字auto的功能类似,也可以自动推导出给定表达式的类型,但它和auto的语法有些不同,auto推导的表达式放在“=”的右边,并作为auto所定义的变量的初始值,而decltype是和表达式结合在一起,语法如下:
decltype(expr) var;
它的语法像是函数调用,但它不是函数调用而是运算符,和sizeof运算符类似,在编译期间计算好,表达式expr不会被真正执行,因此不会产生汇编代码,如下的代码:
int func(int);
decltype(func());
func函数不会真正被调用,只会在编译期间获取他的类型。decltype和auto在功能上大部分相似,但推导规则和应用场景存在一些区别,如用auto定义变量时必须提供初始值表达式,利用初始值表达式推导出类型并用它作为变量的初始值,而decltype定义变量时可以不需要初始值。还有使用auto作为值语义的推导时,会忽略表达式expr的引用性和CV属性,而decltype可以保留这些属性,关于auto的详细解析,可以参考另一篇文章《深入解析C++的auto自动类型推导》。
decltype在普通代码中应用并不广泛,主要用在泛型编程中较多,因此没有auto使用得多,下面将介绍decltype的推导规则,在介绍过程中遇到和auto规则不一样的地方则将两者对照说明,最后再介绍decltype无法被auto替代的应用场景。
推导规则
我将decltype的推导规则归纳为两条,根据expr有没有带小括号分为两种形式,如以下的形式:
decltype(expr)
// 或者
decltype((expr))
- expr没有带括号的情形
当expr是单变量的标识符、类的数据成员、函数名称、数组名称时,推导出来的结果和expr的类型一致,并且会保留引用属性和CV修饰词,如下面的例子:
int func(int, int) {
int x;
return x;
}
class Base {
public:
int x = 0;
};
int x1 = 1; // (1) decltype(x1)为int
const int& x2 = 2; // (2) decltype(x2)为const int&
const Base b;
b.x; // (3) decltype(b.x)为int
int a[10]; // (4) decltype(a)为int[10]
decltype(func); // (5) 结果为int(int, int)
(1)式decltype(x1)的结果和x1的类型一致,为int类型。
(2)式的结果也是和x2一致,这里和auto的推导规则不同的是,它可以保留x2的引用属性和const修饰词,所以它的类型是const int&。
(3)式中定义的类对象b虽然是const的,但成员x的类型是int类型,所以结果也是int。
(4)和(5)都保留了原本的类型,这个也是和auto的推导结果不同的,使用auto推导的规则它们会退化为指针类型,这里则保留了它们数组和函数的类型。
当expr是一条表达式时,decltype(expr)的结果视expr表达式运算后的结果而定(在编译时运算而非运行时运算),当expr返回的结果是右值时,推导的结果和返回结果的类型一致,当expr返回的结果是左值时,推导的结果是一个引用,见下面的例子:
int x1 = 1;
int x2 = 2;
decltype(x1 + x2); // (1) int
decltype(func()); // (2) int
decltype(x1,x2); // (3) int&
decltype(x1,0); // (4) int
decltype(a[1]); // (5) int&
(1)式因为两个变量相加后返回一个数值,它是一个右值,所以推导结果和它的类型一致,这里换成加减乘除都是一样的。
(2)是一个函数调用,跟上面的使用函数名称不同,这里会调用函数(编译时),根据函数的返回结果来确定推导出来的类型,如果返回结果是引用或者指针类型,则推导结果也会引用或者指针类型,此函数返回的结果是int型,所以结果也是int型。
(3)和(4)是逗号表达式,它的返回结果是逗号后的那个语句,(3)是返回x2,它是一个变量,是一个左值,所以推导结果是int&,而(4)的返回结果是0,是一个右值,因此结果和它的类型一致。
(5)是访问数组中的元素,它是一个左值,因此推导结果是一个引用。
- expr带括号的情形
当expr带上括号之后,它的推导规则有了变化,表达式加上括号后相当于去执行这条语句然后根据返回结果的类型来推导,见下面的例子:
class Base {
public:
int x = 0;
};
int x1 = 1;
int x2 = 2;
const Base b;
b.x;
decltype((x1+x2)); // (1) int
decltype((x1)); // (2) int&
decltype((b.x)); // (3) const int&
(1)式中相加后的结果是一个右值,加上括号后依然是一个右值,因此推导结果是int。
(2)式中跟之前没有加括号的情况不一样,加上括号相当于是返回x1变量,因此是一个左值,推导结果是一个引用。
(3)式中也跟之前的结果不一样了,加上括号相当于返回类的数据成员x,因此是一个左值,推导结果是一个引用,但因为定义的类对象b是一个const对象,要保持它的内容不可被修改,因此引用要加上const修饰。
最后还有要注意一点的是,decltype和auto一样也可以和&和一起结合使用,但和auto的规则不一样,auto与&和结合表示定义的变量的类型是一个引用或者指针类型,而decltype则是保留这个符号并且和推导结果一起作为最终的类型,见下面的例子:
int x1 = 1;
auto *pi = &x1; // (1) auto为int,pi为int*
decltype(&x1) *pp; // (2) decltype(&x1)为int*,pp为int**
(1)式中的auto推导结果为int而不是int,要将pi定义为指针类型需要明确写出auto。
(2)式的decltype(&x1)的推导结果为int,它会和定义中的(*pp前面的星号)结合在一起,因此最终的结果是int**。
decltype的使用场景
上面提到decltype和auto的一个区别就是使用auto必须要有一个初始值,而decltype在定义变量时可以不需要初始值,这在定义变量时暂时无法给出初始值的情况下非常有用,见下面的例子:
#include <map>
#include <string>
template<typename ContainerT>
class Object {
public:
void init(ContainerT& c) { it_ = c.begin(); }
private:
decltype(ContainerT().begin()) it_;
};
int main() {
std::map<std::string, int> m;
Object<std::map<std::string, int>> obj;
obj.init(m);
}
在定义类的成员it_时还没有初始值,这时无法使用auto来推导它的类型,况且这里也无法使用auto来定义类的数据成员,因为现在还不支持使用auto来定义非静态的数据成员的,但使用decltype却是可以的。
还有一种情形是使用auto无法做到的,就是auto在使用值语义的推导规则的时候会忽略掉引用属性和CV修饰词,比如:
int i = 1;
const int& j = i;
auto x = j; // auto的结果为int
这里x无法推导出和变量j一样的类型,你可能会说,如果要使用引用类型,那可以这样写:
const auto& x = j; // auto的结果为int, x的类型const int&
但这又会带来其它的问题,这样定义出来的变量的类型永远都是const引用的类型,无法做到根据不同的表达式推导出相应的类型,如果使用decltype则可以做到:
int i = 1;
const int& j = i;
decltype(j) x = j; // x的类型为const int&
decltype(i) y = i; // y的类型为int
上面的代码使用decltype就可以根据不同的初始值表达式来推导出不同的结果。但你可能会觉得初始值表达式要在左右两边写上两遍,比较累赘,单个变量的还好,如果是个长表达式的话就会显得代码很冗余,也不优雅,比如:
int x = 1;
int y = 2;
double z = 5.0;
decltype(x + y + z) i = x + y + z;
如果上面的例子中表达式再长点就更难看也更麻烦了,幸好C++14标准提出了decltype和auto结合的功能,也就是decltype(auto)的用法。
decltype(auto)的使用解析
自动推导表达式的结果的类型
decltype(auto)的使用语法规则如下:
decltype(auto) var = expr;
它的意思是定义一个变量var,auto作为类型占位符,使用自动类型推导,但推导的规则是按照decltype的规则来推导。因此上面的代码可以这样来写:
decltype(auto) j = x + y + z;
它的用法跟使用auto一样,利用右边的表达式来推导出变量j的类型,但是推导规则使用的是decltype的规则。这对需要保持右边表达式的引用属性和CV修饰词时就非常有用,上面的代码可以改为:
int i = 1;
const int& j = i;
decltype(auto) x = j; // x的类型为const int&
decltype(auto) y = i; // y的类型为int
decltype(auto)用于推导函数返回值的类型
decltype(auto)可以用于推导函数返回值的类型,auto也可以用于推导函数的返回值类型,在讲解auto的那篇文章中就已讲解过。但auto有个问题就是会忽略掉返回值的引用属性,但如果你用auto&来推导返回值类型的话,那所有的类型都将是引用类型,这也不是实际想要的效果,有没有办法做到如果返回值类型是值类型时就推导出值类型,如果返回值类型是引用则推导出结果是引用类型?假设有一个处理容器元素的函数,它接受一个容器的引用和一个索引,函数处理完这个索引的元素之后再返回这个元素,一般来说,容器都有重载了“[]"运算符,但有的容器可能返回的是这个元素的值,有的可能返回的是元素的引用,如:
T& operator[](std::size_t index);
// 或者
T operator[](std::size_t index);
这时我们就可以用decltype(auto)来自动推导这个函数的返回值类型,函数的定义如下:
template<typename Container, typename Index>
decltype(auto) process(Container& c, Index i) {
// processing
return c[i];
}
当传进来的容器的operator[]函数返回的是引用时,则上面的函数返回的是引用类型,如果operator[]函数返回的是一个值时,则上面的函数返回的是这个值的类型。
decltype(auto)使用陷阱
最后,对于decltype(auto)能够推导函数返回值为引用类型这一点,需要提醒一下的是,小心会有下面的陷阱,如下面的函数:
decltype(auto) func() {
int x;
// do something...
return x;
}
这里推导出来的返回值类型是int,并且会拷贝局部变量x的值,这个没有问题。但如果是这样的定义:
decltype(auto) func() {
int x;
// do something...
return (x);
}
这个版本返回的是一个引用,它将引用到一个即将销毁的局部变量上,当这个函数返回后,所返回的引用将引用到一个不存在的变量上,造成引用空悬的问题,程序的结果将是未知的。无论是有意的还是无意的返回一个引用,都要特别小心。
此篇文章同步发布于我的微信公众号:深入解析decltype和decltype(auto)
如果您感兴趣这方面的内容,请在微信上搜索公众号iShare爱分享或者微信号iTechShare并关注,或者扫描以下二维码关注,以便在内容更新时直接向您推送。
深入解析decltype和decltype(auto)的更多相关文章
- c++11-17 模板核心知识(九)—— 理解decltype与decltype(auto)
decltype介绍 为什么需要decltype decltype(auto) 注意(entity) 与模板参数推导和auto推导一样,decltype的结果大多数情况下是正常的,但是也有少部分情况是 ...
- [Effective Modern C++] Item 3. Understand decltype - 了解decltype
条款三 了解decltype 基础知识 提供一个变量或者表达式,decltype会返回其类型,但是返回的内容会使人感到奇怪. 以下是一些简单的推断类型: ; // decltype(i) -> ...
- 解剖Nginx·自动脚本篇(1)解析配置选项脚本 auto/options
在安装Nginx之前(即运行make脚本之前),首先是进行安装的配置准备,包括环境检查及生成文件.这些工作是由自动脚本完成的.和绝大多数软件一样,Nginx的自动脚本的入口,同样是名为configur ...
- C++ 11 学习1:类型自动推导 auto和decltype
Cocos 3.x 用了大量的C++ 11 的东西,所以作为一个C++忠实粉丝,有必要对C++ 11进行一个系统的学习. 使用C++11之前,一定要注意自己使用的编译器对C++11的支持情况,有些编译 ...
- C++11 auto and decltype
1.auto关键字 C++新标准引入auto关键词,此auto与之前C语言的auto意义已经不一样了. 这里的auto是修饰未知变量的类型,编译器会通过此变量的初始化自动推导变量的类型. 例如:aut ...
- auto 和 decltype
一, auto 1, auto的作用 一般来说, 在把一个表达式或者函数的返回值赋给一个对象的时候, 我们必须要知道这个表达式的返回类型, 但是有的时候我们很难或者无法知道这个表达式或者函数的 ...
- c++11——auto,decltype类型推导
c++11中引入了auto和decltype关键字实现类型推导,通过这两个关键字不仅能够方便的获取复杂的类型,而且还能简化书写,提高编码效率. auto和decltype的类型推导都是编译器在 ...
- C++ 11常见功能介绍:auto,decltype,nullptr,for,lambda
什么是C++11 C++11是曾经被叫做C++0x,是对目前C++语言的扩展和修正,C++11不仅包含核心语言的新机能,而且扩展了C++的标准程序库(STL),并入了大部分的C++ Technical ...
- auto和decltype的用法总结
一, auto 1, auto的作用 一般来说, 在把一个表达式或者函数的返回值赋给一个对象的时候, 我们必须要知道这个表达式的返回类型, 但是有的时候我们很难或者无法知道这个表达式或者函数的 ...
- C++ 处理类型名(typedef,auto和decltype)
随着程序越来越复杂,程序中用到的类型也越来越复杂,这种复杂性体现在两个方面.一是一些类型难于"拼写",它们的名字既难记又容易写错,还无法明确体现其真实目的和含义.二是有时候根本搞不 ...
随机推荐
- Python-Json异常:Object of type Decimal is not JSON serializable
源起: 使用python分离出一串文本,因为是看起来像整数,结果json转换时发生异常:TypeError: Object of type Decimal is not JSON serializab ...
- B. Ela's Fitness and the Luxury Number
思路: \[能想到平方是比较特殊的,因为x*x一定是x的倍数也就是说\sqrt[2]{x*x} = {x} \] \[所以需要考虑平法之间的数手模一下样例可以发现 [x^2 ,(x+1)^2)之间是x ...
- Linux 系统编程从入门到进阶 学习指南
引言 大家好,我是小康 ,今天我们来学习一下 Linux 系统编程相关的知识.Linux 系统编程是连接高级语言和硬件的桥梁,它对深入理解计算机系统至关重要.无论你是打算构建高性能服务器还是开发嵌入式 ...
- Python笔记六之多进程
本文首发于公众号:Hunter后端 原文链接:Python笔记六之多进程 在 Python 里,我们使用 multiprocessing 这个模块来进行多进程的操作. multiprocessing ...
- select 对当前选项显示文本的获取 m.options[m.selectedIndex].text | selectz
select 对当前选项显示文本的获取 m.options[m.selectedIndex].text | selectz <html> <head> <title> ...
- Obsidian 0.15.9 知识笔记 使用说明
我感觉这个软件是一个非常好用的软件,经过初步体验. 全局搜索快捷键 Ctrl + Shift + F 打开快速切换快捷键 Ctrl + O 添加标签 #测试标签 反向链接 Obsidian支持反向链接 ...
- redis三主三从详细搭建过程
搭建Redis三主三从集群的详细步骤如下: 准备环境: 确保你有六台服务器或虚拟机,每台服务器上都已经安装了Redis.这些服务器将用于搭建三主三从的Redis集群. 确保所有服务器之间的网络连接正常 ...
- Atom 编辑器实时预览 HTML 页面经典方法
为什么需要这样一个工具? 每次预览 HTML 页面,都需要打开各种浏览器:哪怕不是调试,只是为了查看下效果:切换来切换去,各种刷新,感觉有些浪费时间:以前用过 DW 的实时预览,感觉这个功能很赞: ...
- 5分钟上手Python爬虫:从干饭开始,轻松掌握技巧
很多人都听说过爬虫,我也不例外.曾看到别人编写的爬虫代码,虽然没有深入研究,但感觉非常强大.因此,今天我决定从零开始,花费仅5分钟学习入门爬虫技术,以后只需轻轻一爬就能查看所有感兴趣的网站内容.广告? ...
- 12_采样格式&音频重采样
采样格式 通过前面学习我们知道FFmpeg和SDL都有自己的采样格式的表达式,那么他们都表示什么意思呢? FFmpeg的采样格式的表达式: enum AVCodecID { ...... AV_COD ...