item 2: 理解auto类型的推导
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢!
博客已经迁移到这里啦
如果你已经读过item 1的模板类型推导,你已经知道大部分关于auto类型推导的知识了,因为,除了一种奇怪的情况外,auto类型推导和template类型推导是一样的。但是为什么会这样?template类型推导涉及模板和函数以及参数,但是auto没有处理这些东西。
是这样的,但是这没关系。从template类型推导到auto类型推导有一个直接的映射关系。这里有一个直接的算法来从template类型推导转换到auto类型推导。
在item 1中,template类型推导是解释通用函数模板:
template<typename T>
void f(ParamType param);
并且这样简单调用:
f(expr);
对于f的调用,编译器用expr来推导T和ParamType的类型。
当一个变量用auto来声明时,auto扮演着的角色相当于template中的T,并且变量的类型相当于ParamType。直接举个例子会更简单一些:
auto x = 27;
这里,x的类型就是auto自身,另一方面,在这个声明中:
const auto cx = x;
类型是const auto,然后:
const auto& rx = x;
的类型是const auto&.为了推导x,cx,rx的类型,编译器表现地好像这里有template的声明,并且用初始化表达式调用相应的template:
tamplate<typename T> //概念上的x的template类型推导
void func_for_x(T param);
func_for_x(27); //概念上的调用:paramType的类型推导
//结果将会是x的类型,T的类型推导就是
//auto自身代表的类型
template<typename T>
void func_for_cx(const T param);
func_for_cx(x);
template<typename T>
void func_for_rx(const T& param);
func_for_rx(x);
就像我说的,auto的类型推导,只有一个例外(我们马上就会讨论),其他的和template的类型推导是一样的。
item 1基于ParamType的不同情况,把template的类型推导分成三种情况。而使用auto来声明变量,变量类型取代了ParamType,所以这里也有三种情况:
- 情况1:变量类型是指针或引用,但是不是universal引用。
- 情况2:变量类型是universal引用。
- 情况3:变量类型不是指针也不是引用。
我们已经看过情况1和情况3的例子了:
auto x = 27; //情况3
const auto cx = x; //情况3
const auto& rx = x; //情况1
你可以把情况2想象成这样:
auto&& uref1 = x; //x是int,并且是左值,
//所以uref1的类型是int&
auto&& uref2 = cx; //cx是const int,并且是左值,
//所以uref2的类型是const int&
auto&& uref3 = 27; //27是int,并且是右值,
//所以uref3的类型是 int&&
item 1讨论了当类型是non-reference时,数组和函数退化成指针的情况,这样的情况也发生在auto类型推导中:
const char name[] = "R. N. Brigs";
//name的类型是 const char[13]
auto arr1 = name; //arr1的类型是const char*
auto& arr2 = name; //arr2的类型是const char()[13]
void someFunc(int, double);
//声明一个函数,类型是void(int, double)
auto func1 = someFunc;
//func1的类型是void(*)(int,double)
auto& func2 = someFunc;
//func2的类型是void()(int, double)
就和你看到的一样,auto类型推导和template类型推导一样,他们本质上是硬币的两边。
除了一个情况下,他们是不同的。我们从观察一个情况开始,如果你想用27声明一个int变量,C++98允许你使用两种不同的语法:
int x1 = 27;
int x2(27);
C++11中,通过它对标准初始化的支持,增加了这些操作:
int x3 = {27};
int x4{27};
总之,四种语法都产生一个结果:一个值为27的int变量。
但是根据item 5的解释,声明变量时,用auto来替换确切的类型有几点好处,所以在上面的变量声明中,用auto来替换int是有好处的。直接的文本替换可以产生这样的代码:
auto x1 = 27;
auto x2(27);
auto x3 = {27};
auto x4{27};
这些声明都能编译,但是在用auto替换后,它们拥有了不同的解释。前两条语句确实声明了值为27的int变量。然而,后面两条语句,声明的变量类型为带有值为27的std::initializer_list !
auto x1 = 27; //类型是int, 值是27
auto x2(27); //同上
auto x3 = {27}; //类型是 std::initializer_list<int>
//值是{27}
auto x4{27}; //同上
产生这样的结果是由于auto的特殊的类型推导规则。当用初始化列表的形式来初始化auto声明的变量时,推导出来的类型就是std::initializer_list,下面这样的代码是错误的:
auto x ={1, 2, 3.0}; //错误! 不能推导
//std::initializer_list<T>中的T
就像注释里说的,这种情况下的类型推导将会失败,但是你需要知道这里其实产生了两种类型推导。一种就来自x5的auto类型推导。因为x5的初始化在花括号中,x5就被推导为std::initializer_list。但是 std::initializer_list是一个template。用T来实例化std::initializer_list意味着T的类型也必须被推导出来。这里发生的推导属于第二种类型推导:template类型推导。在这个例子中,这个推导失败了,因为初始化列表中的值不是同种类型的。
auto类型推导和template类型推导唯一的不同之处,就是对待初始化列表的不同做法。当用初始化列表来声明auto类型的变量时,推导出来的类型是std::initializer_list的一个实例。但是如果传入同样的初始化列表给template,类型推导将会失败,并且代码编译不通过:
auto x = {11, 23, 9}; //类型是std::initializer_list<int>
template<typename T>
void f(T param);
f({11, 23, 9}); //错误!不能推导T的类型
然而如果你明确template的参数为std::initializer_list,template类型推导将成功推导出T:
template<typename T>
void f(std::initializer_list<T> initList);
f({11, 23, 9}) //T被推导为int,并且initList的类型是
//std::initializer_list<int>
所以在auto和template的类型推导的唯一不同之处就是,auto假设初始化列表为std::initializer_list,但是template类型推导不这么做。
你可能想知道为什么auto类型推导对初始化列表有这么一个特别的规则,但是template类型推导却没有。我也想知道,可悲的是,我没有找到一个令人信服的解释。但是规则就是规则,并且这意味着你必须记住,如果你用auto声明一个变量并且用初始化列表来初始化它,那么被推导出来的类型就是std::initializer_list。你尤其要记住接受标准初始化---初始值在花括号中的思想理所当然的。(It’s especially
important to bear this in mind if you embrace the philosophy of uniform initialization—of enclosing initializing values in braces as a matter of course. )。C++11中的一个典型的错误就是,当你想声明一个其它变量时,意外地声明了一个std::initializer_list变量。这个陷阱导致了,有些开发者只在他们必须时才用大括号来初始化变量(什么时候才是必须的情况将在item 7中讨论)
对于C++11来说,故事已经完结了,但是对C++14来说,故事还将继续。C++14允许auto作为函数的返回值,并且在lambdas表达式中可能用auto来作为形参类型。然而,auto的这些用法使用的是template类型推导规则,而不是auto类型推导规则。所以返回一个初始化列表给auto类型的函数返回值将不能通过编译:
auto createInitList()
{
return {1, 2, 3}; //错误!不能推导{1,2,3}的类型
}
在C++14中,这对于作为lambdas表达式的参数声明的auto类型同样适用:
std::vector<int> v;
...
auto resetV = [&v](const auto& newValue)
{ v = newValue; };
...
resetV({1, 2, 3}); //错误!不能推导{1,2,3}的类型
你要记住的事
- auto类型推导常常和template类型推导一样,但是auto类型推导假设初始化列表为std::initializer_list,但是template类型推导不这么做
- auto作为函数返回类型或lambdas表达式的形参类型时,默认使用template类型推导规则,而不是auto类型推导规则。
item 2: 理解auto类型的推导的更多相关文章
- Effective Modern C++ ——条款2 条款3 理解auto型别推导与理解decltype
条款2.理解auto型别推导 对于auto的型别推导而言,其中大部分情况和模板型别推导是一模一样的.只有一种特例情况. 我们先针对auto和模板型别推导一致的情况进行讨论: //某变量采用auto来声 ...
- 现代C++之理解auto类型推断
理解auto类型推断 上一篇帖子中讲述了模板类型推断,我们知道auto的实现原理是基于模板类型推断的,回顾一下模板类型推断: template <typename T> void f(Pa ...
- item 1:理解template类型的推导
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 一些用户对复杂的系统会忽略它怎么工作,怎么设计的,但是很高兴去知道它完成的一些事.通过这 ...
- Effective Modern C++ 条款2:理解auto型别推导
在条款1中,我们已经了解了有关模板型别的推导的一切必要知识,那么也就意味着基本上了解了auto型别推导的一切必要知识. 因为,除了一个奇妙的例外情况,auto型别推导就是模板型别推导.尽管和模板型别推 ...
- 第2课 auto类型推导(1)
第2课 auto类型推导(1) 一.auto类型推导 (一)与模板类型推导映射关系 1.auto类型推导与模板类型推导可以建立一一映射关系,它们之间存在双向的算法变换.auto扮演模板中T的角色,而变 ...
- 《Effective Modern C++》翻译--条款2: 理解auto自己主动类型推导
条款2: 理解auto自己主动类型推导 假设你已经读过条款1关于模板类型推导的内容,那么你差点儿已经知道了关于auto类型推导的所有. 至于为什么auto类型推导就是模板类型推导仅仅有一个地方感到好奇 ...
- item 4: 知道怎么去看推导的类型
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 对于推导类型结果的查看,根据不同的软件开发阶段,你想知道的信息的不 ...
- Effective Modern C++翻译(3)-条款2:明白auto类型推导
条款2 明白auto类型推导 如果你已经读完了条款1中有关模板类型推导的内容,那么你几乎已经知道了所有关于auto类型推导的事情,因为除了一个古怪的例外,auto的类型推导规则和模板的类型推导规则是一 ...
- auto类型推导
引言 auto : 类型推导. 在使用c++的时候会经常使用, 就像在考虑STL时迭代器类型, 写模板的时候使用auto能少写代码, 也能帮助我们避免一些隐患的细节. auto初始化 使用auto型别 ...
随机推荐
- python网络编程:socket、服务端、客户端
本文内容: socket介绍 TCP: 服务端 客户端 UDP: 服务端 客户端 首发时间:2018-02-08 01:14 修改: 2018-03-20 :重置了布局,增加了UDP 什么是socke ...
- tkinter中lable标签控件(二)
lable控件 对于tkinter来说,学起来很简单,只要设置好相应的参数即可出结果,所以不用刻意去记住这些参数.学习一遍后理解每个参数的作用是什么即可. 当下次用到的时候来笔记上看一下就行. 内容很 ...
- python第二十九天-----继续学习第三模块——前几天旅行去了
subprocess模块 import subprocess subprocess.getstatusoutput('dir')#接收字符串格式命令,返回元组形式,第1个元素是执行状态,第2个是命令结 ...
- 描述性统计的matlab实现
理论讲的再多不会做也白弄 直接上手 一.针对接近正态分布的(均值,方差,标准差,极差,变异系数,偏度,峰度) 这里我必须提前说明一点就是,你在写好函数后,函数的名是dts,你保存的文件名也必须是dts ...
- Linux 用户名、主机添加背景色
文章参考:PS1应用之——修改linux终端命令行各字体颜色 Linux 用户名.主机添加背景色,用于生产环境,这样可以减少人为的误操作. [root@zhang ~]# tail /etc/bash ...
- [Tomcat]The JRE_HOME environment variable is not defined correctly
在tomcat的bin目录下,双击startup.bat,闪一下,就没了,后来仔细看了一下黑屏闪的内容如下: the JRE_HOME environment variable is not defi ...
- kafka的Java客户端示例代码(kafka_2.12-0.10.2.1)
使用0.9开始增加的KafkaProducer和KafkaConsumer. Pom.xml <project xmlns="http://maven.apache.org/POM/4 ...
- 如何根据name和value选中radio [问题点数:40分,结帖人zzxap
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <he ...
- MySQL-proxy代理导致PHP PDO::ATTR_EMULATE_PREPARES的预处理出错,MySQL报General error: 1243错误
背景: 用的ThinkPHP5的框架.(相比之前的3.2版本,版本5都用了PDO处理数据库) 症状: 报错信息: SQLSTATE[HY000]: General error: 1243 Unknow ...
- Spring配置文件中的那些标签意味着什么(持续更新)
前言 在看这边博客时,如果遇到有什么不清楚的地方,可以参考我另外一边博文.Spring标签的探索,根据这边文章自己来深入源码一探究竟.这里自己只是简单记录一下各标签作用,每个人困惑不同,自然需求也不一 ...