Effective Modern C++翻译(3)-条款2:明白auto类型推导

条款2 明白auto类型推导

如果你已经读完了条款1中有关模板类型推导的内容,那么你几乎已经知道了所有关于auto类型推导的事情,因为除了一个古怪的例外,auto的类型推导规则和模板的类型推导规则是一样的,但是为什么会这样呢?模板的类型推导涉及了模板,函数和参数,但是auto的类型推导却没有涉及其中的任何一个。

 

这确实是对的,但这无关紧要,在auto类型推导和template之间存在一个直接的映射,可以逐字逐句的将一个转化为另外一个。

 

在条款1中,模板类型推导是以下面的模板形式进行举例讲解的:

template<typename T>
void f(ParamType param);

函数调用是这样

f(expr); //用一些表达式调用函数f

在f的函数调用中,编译器使用expr来推导T和ParamType的类型。

 

当一个变量用auto进行声明的时候,auto扮演了模板中的T的角色,变量的类型说明符(The type specifier)相当于ParamType,这个用一个例子来解释会更容易一些,考虑下面的例子:

auto x=27;

这里x的类型说明符就是auto本身,在另一方面,在下面这个声明中:

const auto cx=x;

类型说明符是const auto。

const auto& rx=x;

类型说明符是const auto&,在上面的例子中,为了推导x,cx,rx的类型,编译器会假装每一个声明是一个模板,并且用相应的初始化表达式来调用(compilers act as if there were a template for each declaration as well as a call to that template with the corresponding initializing expression:)

template<typename T>              // 产生概念上的模板来
void func_for_x(T param);         // 推导x的类型
func_for_x(27);                   // 概念上的函数调用,参数
                                  // 推导出的类型就是x的类型
template<typename T>              // 产生概念上的模板来
void func_for_cx(const T param);  // 推导cx的类型
func_for_cx(x);                   // 概念上的函数调用,参数
                                  // 推导出的类型就是cx的类型
template<typename T>              // 产生概念上的模板来
void func_for_rx(const T& param); // 推导cx的类型
func_for_rx(x);                   // 概念上的函数调用,参数
                                  // 推导出的类型就是rx的类型

就像我说的那样,auto的类型推导和模板的类型推导是一样的。

 

条款1把模板的类型推导按照ParamType的类型,分成了3种情况,同样,在auto声明的变量中,变量的类型说明符(The type specifier)相当于ParamType,所以auto类型推导也有3种情况:

  • 情况1:类型说明符是一个指针或是一个引用,但不是一个万能引用(universal reference)
  • 情况2:类型说明符是一个万能引用(universal reference)
  • 情况3:类型说明符既不是指针也不是引用

 

我们在上面已经举过了情况1和情况3的例子

auto x = 27;        //条款3(x既不是指针也不是引用) 
const auto cx = x;  //条款3(cx既不是指针也不是引用)
const auto& rx = x; //条款1(rx不是一个万能引用)

情况2也像你想的那样

auto&& uref1 = x;    // x的类型是int并且是一个左值
                     // 所以uref1的类型是
auto&& uref2 = cx;   // cx的类型是const int并且是一个左值
                     // 所以uref2的类型是const int&

auto&& uref3 = 27;   // 27的类型是int并且是一个右值
                     // 所以uref3的类型是int&&  

条款1同样也讨论了数组和函数名在非引用类型的类型说明符下,会退化为指针类型,这当然同样适用于auto的类型推导

const char name[] = "R. N. Briggs"; //name的类型是const char[13] name's type is const char[13]

auto arr1 = name;                   //arr1的类型是const char* 
auto& arr2 = name;                  //arr2的类型是
                                    // const char (&)[13]
void someFunc(int, double);         //someFunc是一个函数;
                                    //类型是void(int, double)
auto func1 = someFunc;              // func1的类型是
                                    // void (*)(int, double)
auto& func2 = someFunc;             // func2的类型是
                                    // void (&)(int, double)

就像你看到的那样,auto类型推导其实和模板类型推导是一样的,他们就相当于硬币的正反两个面。

 

但是在一点上,他们是不同的,如果你想把一个声明一个变量,它的初始值是27,C++98中,你可以使用下面的两种语法

int x1 = 27;
int x2(27);

在C++11中,提供对统一的集合初始化(uniform initialization)的支持,增加下面是声明方式。

int x3 = {27};
int x4{27};

总而言之,上面的4种声明方式的结果是一样的,声明了一个变量,它的初始值是27。

 

但是就像条款5解释的那样,使用auto声明变量要比使用确定的类型声明更有优势,所以将上面代码变量声明中的int替换成auto会是非常好的,直接的文本上的替换产生了下面的代码:

auto x1 = 27;
auto x2(27);
auto x3 = {27};
auto x4{27};

这些声明都能够通过编译,但他们并非全和替代前有着同样的意义,前两个的确声明了一个int类型的变量,初始值为27;然而,后两个声明了一个std::initializer_list<int>类型的变量,它包括一个元素,初始值是27;

auto x1 = 27;    // 类型是int,初始值是27
auto x2(27);     // 同上
auto x3 = {27};  //类型是std::initializer_list<int>//初始值是27
auto x4{27};     //同上

 

这是由于auto类型推导的一个特殊的规则,当变量使用大括号的初始化式(braced initializer)初始化的时候,被推导出的类型是std::initializer_list,如果这个类型不能被推导出来(比如,大括号的初始化式中的元素有着不同的类型),代码将不能通过。

auto x5 = {1, 2, 3.0}; // 错误!无法推导出std::initializer_list<T>中T的类型


就像注释里指出的的那样,类型推导在这种情况下失败了,但是,重要的是认识到这里其实发生了两种形式的类型推导,一种来源于auto的使用,x5的类型需要被推导出来,另外因为auto是用大括号的初始化式初始化的,x5的类型必须被推导为std::initializer_list,但是std::initializer_list是一个模板,所以实例化模板std::initizalizer_list<T>意味着T的类型必须被推导出来,在上面的例子中,模板的类型推导失败了,因为大括号里变量类型不是一致的。

 

对待大括号的初始化式(braced initializer)的不同是auto类型推导和模板类型推导的唯一区别,当auto变量用一个大括号的初始化式(braced initializer)初始化的时候,推导出的类型是实例化后的std::initializer_list模板的类型,而模板类型推导面对大括号的初始化式(braced initializer)时,代码将不会通过(这是由于完美转发perfect forwarding的结果,将在条款32中进行讲解)

 

你可能会猜想为什么auto类型推导对于大括号的初始化式(braced initializer)有着特殊的规则,而模板类型推导确没有,我也想知道,不幸的是,我没有找到一个吸引人的解释,但是规则就是规则,这意味着,你必须记住如果你用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类型的变量,但他们的本意却是想声明一个其他类型的变量。让我再重申一下:

auto x1 = 27;    // x1和 x2都是int类型
auto x2(27);
auto x3 = {27};  // x3和x4是
auto x4{27};     // std::initializer_list<int>类型

 

陷阱的主要原因是一些程序员只有当必要的时候,才使用大括号的初始化式进行初始化)(This pitfall is one of the reasons some developers put braces around their initializers only when they have to. (什么时候你必须时候将在条款7中讨论)

 

对于C++11,这已经是一个完整的故事了,但是对于C++14,故事还没有结束,C++14允许auto来指出一个函数的返回类型需要被推导出来(见条款3),C++14的lambda表达式可能需要在参数的声明时使用auto,不管怎样,这些auto的使用,采用的是模板类型推导的规则,而不是auto类型推导规则,这意味着,大括号的初始化式会造成类型推导的失败,所以一个带有auto返回类型的函数如果返回一个大括号的初始化式将不会通过编译。

auto createInitList()
{
return { 1, 2, 3 };  // 错误: 无法推导出
}                    // { 1, 2, 3 }的类型

同样,规则也适用于当auto用于C++14的lambda(产生一个通用的lambda(generic lambda))的参数类型说明符时,

std::vector v;auto resetV =
[&v](const auto& newValue) { v = newValue; }; //只在C++14下允许 
…
resetV( { 1, 2, 3 } );                        //错误! 无法推导出
                                              //{ 1, 2, 3 }的类型

 

最终结果是auto类型推导和模板类型推导是完全相同的,除非(1)一个变量被声明了,(2)它的初始化是用大括号的初始化式进行初始化的(its initializer is inside braces),只有这种情况下,auto下被推导为std::initializer_list,而模板会失败。

 

请记住:

  • auto的类型推导通常和模板类型推导完全相同。
  • 唯一的例外是,当变量用auto声明,并且使用大括号的初始化式初始化时,auto被推导为std::initializer_list。
  • 模板类型推导在面对大括号的初始化式(braced initializer)初始化时会失败。

转载于:https://www.cnblogs.com/magicsoar/p/3974659.html

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

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

相关文章

信息论与编码复习

若信源有m种消息&#xff0c;且每个消息是以相等可能产生的&#xff0c;则该信源的信息量可表示为Ilogm。 信息率是通过接收到的信息可获得的发送信息的信息量,即互信息。单位:bit/符号。 信息速率是单位时间内传输的信息量。单位:bit/s 码字CodeWord。由若干个码元组成&#x…

拖拽碰撞效果最终版

拖拽碰撞效果最终版&#xff0c;没准还有bug&#xff0c;不过现在在各个浏览器下效果是对的&#xff0c;代码需要精简一些&#xff0c;以后有时间了在弄吧&#xff0c;现在先不理了&#xff0c;感冒了&#xff0c;没有心情整理 <!DOCTYPE HTML> <html lang"en-US…

Python 如何利用函数修改函数外list?

#在函数内修改列表的时候&#xff0c;在列表后面加上[:]&#xff0c;无论几维列表均可。 def foo(listA):listA[:] [1,2,3] def foo2(listB):listB [1,2,3] listA [4,5,6] listB [4,5,6] foo(listA) foo2(listB) print listA #result: [1,2,3] print listB #result: [4,5,6…

图片压缩android bitmap compress(图片压缩)

本文纯属个人见解&#xff0c;是对前面学习的总结&#xff0c;如有描述不正确的地方还请高手指正~ 有些场景中&#xff0c;须要照相并且上传到服务&#xff0c;但是由于图片的巨细太大&#xff0c;那么就 上传就 会很慢(在有些网络情况下)&#xff0c;而且很耗流量&#xff0c;…

linux进程间通信快速入门【一】:管道编程

介绍 管道本质上就是一个文件&#xff0c;前面的进程以写方式打开文件&#xff0c;后面的进程以读方式打开。这样前面写完后面读&#xff0c;于是就实现了通信。虽然实现形态上是文件&#xff0c;但是管道本身并不占用磁盘或者其他外部存储的空间。在Linux的实现上&#xff0c;…

返回长度hdu 1518 square

查了好多资料&#xff0c;发现还是不全&#xff0c;干脆自己整理吧&#xff0c;至少保证在我的做法正确的&#xff0c;以免误导读者&#xff0c;也是给自己做个记载吧&#xff01; 题目的意思是比较明显的&#xff0c;就是当初给你m根木棒&#xff0c;当初让你判断利用这些木棒…

POJ 3233 Matrix Power Series 矩阵快速幂 + 二分

题意&#xff1a;求矩阵的次方和 解题思路&#xff1a;最容易想到方法就是两次二分因为 我们可以把一段 A^1 A^2 .......A^K 变成 A^1 ..A^(K/2) ( A^1 ..A^(K/2))*(A^(k/2)) 当k 为奇数的时候 或者 A^1 ..A^(K/2) ( A^1 ..A^(K/2))*(A^(k/2)) A^K 当K 为偶数的时候…

时间序列进行分析的一些手法以及代码实现(移动平均、指数平滑、SARIMA模型、时间序列的(非)线性模型)

文章目录1、移动平均moving average方法weighted average方法2、指数平滑单指数平滑 exponential_smoothing双指数平滑三指数平滑 Triple exponential smoothing3、平稳性以及时间序列建模SARIMA模型4、时间序列的&#xff08;非&#xff09;线性模型时间序列的滞后值使用线性回…

政权组织形式

神马国家结构、政权组织形式的 现在我比较明确的是英国的政权组织形式是式君主立宪制、美国是总统制、中国是人民代表大会制。 目前,世界各国采用的国家结构可分为单一制和复合制两大类。其中&#xff0c;复合制国家结构形式主要包括联邦制和邦联制两种类型。英国、法国、意大利…

三大平衡树(Treap + Splay + SBT)总结+模板

Treap树 核心是 利用随机数的二叉排序树的各种操作复杂度平均为O(lgn) Treap模板&#xff1a; #include <cstdio> #include <cstring> #include <ctime> #include <iostream> #include <algorithm> #include <cstdlib> #include <cmath…

mysqld进程 ut_delay 占用率过高

采用性能分析工具perf top -p mysqld进程 在测试mysql数据库时&#xff0c;用perf top如果看到热点函数是ut_delay或者_raw_spin_lock的话&#xff0c;说明锁争用比较严重。 ut_delay这是innodb的一个自旋琐。也就是说&#xff0c;在这里由于锁等待&#xff0c;innodb不停地在…

TClientDataSet使用要点

TClientDataSet控件继承自TDataSet&#xff0c;其数据存储文件格式扩展名为 .cds&#xff0c;是基于文件型数据存储和操作的控件。该控件封装了对数据进行操作处理的接口和功能&#xff0c;而本身并不依赖上述几种数据库驱动程序&#xff0c;基本上能满足单机"瘦"数据…

滑动窗口在重构数据集的作用

step1&#xff1a;使用滑动窗口重构数据集 给定时间序列数据集的数字序列&#xff0c;我们可以将数据重构为看起来像监督学习问题。 我们可以通过使用以前的时间步作为输入变量并使用下一个时间步作为输出变量来做到这一点。 通过观察重构后的数据集与原本的时间序列&…

sliverlight - Unhandled Error in Silverlight Application错误

使用firebug控制台输出错误&#xff1a; Unhandled Error in Silverlight Application 查询“GetFlow_Process”的 Load 操作失败。远程服务器返回了错误: NotFound。 位于 System.ServiceModel.DomainServices.Client.OperationBase.Complete(Exception error) 位于 System.S…

前向验证对于模型的更新作用

首先&#xff0c;让我们看一个小的单变量时间序列数据&#xff0c;我们将用作上下文来理解这三种回测方法&#xff1a;太阳黑子数据集。该数据集描述了刚刚超过 230 年&#xff08;1749-1983 年&#xff09;观察到的太阳黑子数量的每月计数。 数据集显示了季节之间差异很大的…

2014年9月21日_随笔,jdic,ETL,groovy,Nutz好多东西想学

&#xff08;1&#xff09;老妈十一要回老家&#xff0c;才突然发现买票好难啊。有亲朋很重要 &#xff08;2&#xff09;这周我做了什么。jdic,ETL,groovy, Nutz好多东西想学。 Nutz开发成员专访、Nutz优酷视频(演讲)、Nutz 入门教程、 &#xff08;3&#xff09;想改变&#…

PHP-面向对象(八)

1、多态的介绍与优势 多态性是继抽象和继承后&#xff0c;面向对象语言的第三个特征。从字面上理解&#xff0c;多态的意思是“多种形态”&#xff0c;简单来说&#xff0c;多态是具有表现多种形态的能力的特征&#xff0c;在OO中是指“语言具有根据对象的类型以不同方式处理。…

双指数平滑中参数对于预测模型的影响

先看看α 在β一致的情况下&#xff0c;α越小&#xff0c;模型越滞后。 再看看β 在α一致的情况下&#xff0c;β越大&#xff0c;模型对于趋势的预测更敏锐。

SQL 性能不佳的几个原因

SQL 性能不佳的几个原因 •不准确的统计数据•差劲的索引•差劲的查询设计 •差劲的执行计划&#xff0c;通常是由不正确的参数引起的•过度阻塞和死锁 •非基于集合的操作•不良数据库设计 •过度碎片 •不能重复使用执行计划 •查询频繁重编译 •不当使用游标 •数据库日志的…

分页查询

分页查询算是比较常用的一个查询了在DAO层主要是查两个数据第一个总条数第二个要查询起始记录数到查询的条数当第一次点击查询时候(非下一页时Page类里面预设的就是 index就是0 pageSize是预设值当点击下一页的时候 index 和 pageSize带的就是页面上面给的值了分页的页面一般的…