条款2: 理解auto自己主动类型推导

假设你已经读过条款1关于模板类型推导的内容,那么你差点儿已经知道了关于auto类型推导的所有。

至于为什么auto类型推导就是模板类型推导仅仅有一个地方感到好奇。那是什么呢?即模板类型推导包含了模板、函数和參数,而auto类型判断不用与这些打交道。

这当然是真的。可是没关系。

模板类型推导和auto自己主动类型推导是直接匹配的。

从字面上看,就是从一个算法转换到还有一个算法而已。

在条款1中。阐述模板类型推导採用的是常规的函数模板:

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

而且是常规方法进行调用:

f(expr);    //call f with some expression

在调用f的过程中。编译器通过expr推导出T和ParamType的类型。

当使用auto关键字声明一个变量时。auto关键字就扮演者上述模板中T的角色,而且类型说明符与ParamType扮演者相同的角色。比语言描写叙述更加清晰的是展示他们。所以请看这种样例:

auto x = 27;

这样,x的类型说明符与自己一样。

还有一方面,这样声明:

const auto cx = x;

这里的类型说明符是const auto。而这里:

const auto& rx = x;

此时类型说明符是const auto&。为了判断上面这些样例中的x,cx和rx变量,编译器起到的作用就是为每一个声明提供一个模板,而且使用对应的初始化表达式来调用这些模板:

template<typename T>        //conceptual template for deducing x's type
void func_for_x(T param); func_for_x(27); //conceptual call: param's deduced type is x's type template<typename T> //conceptual template for deducing cx's type
void func_for_cx(const T param); func_for_cx(x); //conceptual call: param's deduced type is cx's type template<typename T> //conceptual template for deducing rx's type
void func_for_rx(const T& param); func_for_rx(x); //conceptual call: param's deduced type is rx's type

正如我之前所说,auto类型推导与模板类型推导是一样的。

条款1中依据ParamType和在常规模板中param的类型说明符,把模板类型推导分为三种情况。

在通过auto进行变量类型推导时。类型说明符替代了ParamType,但也是分为三个情况:

•第一种情况:类型说明符是一个指针或是引用,但不是universal reference。

•另外一种情况:类型说明符是一个universal reference。

•第三种情况:类型说明符既不是指针也不是引用。

我们分别看看第一和第三种情况的样例:

auto x = 27;           //case 3 (x is neither ptr nor reference)

const auto cx = x;     //case 3 (cx isn't either)

const auto& rx = x;    //case 1 (rx is non-universal ref.)

另外一种情况正如你期待的那样:

auto&& uref1 = x;       //x is int and lvalue, so uref1's type is int&

auto&& uref2 = cx;      //cx is const int and lvalue, so uref2's type is const int&

auto&& uref3 = 27;      //27 is int and rvalue, so uref3's type is int&&

条款1中总结了对于非引用类型的说明符,数组和函数名怎样退化为指针。这当然相同适用于auto类型推导:

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

auto arr1 = name;                    //arr1's type is const char*

auto& arr2 = name;                   //arr2's type is const char (&)[13]

void someFunc(int, double);       //someFunc is a function; type is void(int, double)

auto func1 = someFunc;              //func1's type is void(*)(int, double)

auto& func2 = someFunc;             //func2's type is void(&)(int, double)

正如您所见,auto类型推导真的和模板类型推导是一样的。

就好比一枚硬币的两个面相同。

您肯定期待二者的不同。让我从观察声明一个变量并初始化为27開始,在C++98中。给了你两种语法选择:

int x1 = 27;
int x2(27);

C++11中还添加了这个:

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

总之。四种语法都得到了一个结果。就是把变量初始化为27。

可是正如条款5解释的那样。使用auto取代固定的类型来声明变量有非常多的优势,所以使用auto关键字取代上面程序中的int类型。简单的文本替换我们就得到这种代码:

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

上面的这些都能够通过编译。可是与之前的相比,代表的含义已经不同了。上面四个表达式中前两个实际上是声明一个值为27int类型的变量。而后两个,是声明一个类型为std::initializer_list<int>,而且具有一个值为27的元素!

auto x1 = 27;        //type is int, value is 27

auto x2(27);         //同上

auto x3 = {27};      //type is std::initializer_list<int>, value is {27}

auto x4{27};         //同上

这是由于auto类型推导有特俗的规则。当为auto声明的类型进行初始化使用封闭的大括号时,则推导的类型为std::initializer_list。

假设这种类型不能被推导。这种编码是被编译器拒绝的:

auto x5 = {1, 2, 3.0}; //error! can't deduce T for std::initializer_list<T>

正如上面凝视中提示的那样,这种情况下类型推导会失败。但更重要的是认清此时採用了两种类型推导。一个是通过auto完毕对x5的类型推导。由于x5是使用大括号进行的初始化,所以x5必须被推导为std::initializer_list类型。可是std::initializer_list是一个模板。对于std::initializer_list<int>

的实例化须要推导T的类型。

这种类型推导发生另外一种类型推导的管辖范围:模板类型推导。

在这个样例中,类型推导失败,由于初始化值不具有单一类型。

对待使用大括号进行初始化,auto类型推导和模板类型推导是有差别的。当一个auto变量被大括号进行初始化时,推导的类型为std::initializer_list实例化的类型。假设一个模板面临着对于大括号初始化进行类型推导,这种代码是被拒绝的。(条款32将解释完美的转发)

你会怀疑为什么对于大括号进行初始化。auto类型推导会有特殊的规则呢,而模板类型推导则没有。我自己对此也表示怀疑。不幸的是,我找不到令人信服的解释。

可是规则就是规则,这也就意味着对于auto推导用大括号初始化的变量时你必须铭记于心,推导类型的总是std::initializer_list。这是特别重要的是要牢记这一点,假设你面对在大括号包围初始化值作为理所当然的初始化的理念。

在C++11编程中。当中最典型的错误是你想声明一个其它类型的变量,却声明一个std :: initializer_list变量。重申一下:

auto x1 = 27;    //x1 and x2 are ints
auto x2(27); auto x3 = {27}; //x3 and x4 are std::initializer_list<int>s
auto x4{27};

这个陷阱使得一些开发人员仅仅有迫不得已的时候才使用大括号初始化变量。(我们在条款7中再讨论。)

对于C++11来说。这里是所有的内容,可是对于C++14,故事还在继续。C++14同意使用auto去推导函数的返回值(參见条款3)。而且C++14中的lambda表达式在參数声明时能够使用auto类型推导。可是,这里使用的auto推导採用的是模板推导的规则,而不是auto类型推导的规则。

这就意味着,使用大括号初始化将造成类型推导失败。所以使用auto作为返回类型的函数返回一个使用大括号初始化的变量编译不会通过:

auto createInitList()
{
return {1, 2, 3}; //error: can't deduce type for {1, 2, 3}
}

相同的道理也适用于C++14中lambda表达式使用auto作为參数(因此产生一个通用lambda表达式):

std::vector v;
....
auto resetV = [&v](const auto& newValue) { v = newValue;};//C++14 only
....
resetV({1, 2, 3}); //error! can't deduce type for {1, 2, 3}

终于的结果是,假设不使用大括号进行初始化。auto类型推导是和模板类型推导一致的。仅在这种情况下(使用大括号进行初始化)。auto推导为一个std:: initializer_list,但模板类型推导会失败。

请记住:

•auto类型对象通常与模板类型推导是相同的。

•唯一的例外是:使用auto和大括号进行初始化时,自己主动推导为std::initializer_lists。

•对于使用括号进行初始化,模板类型推导会失败。

《Effective Modern C++》翻译--条款2: 理解auto自己主动类型推导的更多相关文章

  1. Effective Modern C++翻译(6)-条款5:auto比显示的类型声明要更好

        在概念上说,auto关键字和它看起来一样简单,但是事实上,它要更微妙一些的.使用auto会让你在声明变量时省略掉类型,同时也会防止了手动类型声明带来的正确性和性能上的困扰:虽然按照语言预先定义 ...

  2. Effective Modern C++翻译(4)-条款3:了解decltype

    条款3 了解decltype decltype是一个有趣的东西,给它一个变量名或是一个表达式,decltype会告诉你这个变量名或是这个表达式的类型,通常,告诉你的结果和你预测的是一样的,但是偶尔的结 ...

  3. Effective Modern C++翻译(5)-条款4:了解如何观察推导出的类型

    条款4:了解如何观察推导出的类型 那些想要知道编译器推导出的类型的人通常分为两种,第一种是实用主义者,他们的动力通常来自于软件产生的问题(例如他们还在调试解决中),他们利用编译器进行寻找,并相信这个能 ...

  4. Effective Modern C++翻译(1):序言

    /*********************************************************** 关于书: 书是我从网上找到的effective Modern C++的样章,内 ...

  5. Effective Modern C++ ——条款2 条款3 理解auto型别推导与理解decltype

    条款2.理解auto型别推导 对于auto的型别推导而言,其中大部分情况和模板型别推导是一模一样的.只有一种特例情况. 我们先针对auto和模板型别推导一致的情况进行讨论: //某变量采用auto来声 ...

  6. Effective Modern C++翻译(7)-条款6:当auto推导出意外的类型时,使用显式的类型初始化语义

    条款6:当auto推导出意外的类型时,使用显式的类型初始化语义 条款5解释了使用auto来声明变量比使用精确的类型声明多了了很多的技术优势,但有的时候,当你想要zag的时候,auto可能会推导出了zi ...

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

    条款2 明白auto类型推导 如果你已经读完了条款1中有关模板类型推导的内容,那么你几乎已经知道了所有关于auto类型推导的事情,因为除了一个古怪的例外,auto的类型推导规则和模板的类型推导规则是一 ...

  8. Effective Modern C++翻译(2)-条款1:明白模板类型推导

    第一章 类型推导 C++98有一套单一的类型推导的规则:用来推导函数模板,C++11轻微的修改了这些规则并且增加了两个,一个用于auto,一个用于decltype,接着C++14扩展了auto和dec ...

  9. Effective Modern C++  条款1:理解模板型别推导

    成百上千的程序员都在向函数模板传递实参,并拿到了完全满意的结果,而这些程序员中却有很多对这些函数使用的型别是如何被推导出的过程连最模糊的描述都讲不出来. 但是当模板型别推导规则应用于auto语境时,它 ...

随机推荐

  1. 李宏毅机器学习笔记6:Why deep、Semi-supervised

    李宏毅老师的机器学习课程和吴恩达老师的机器学习课程都是都是ML和DL非常好的入门资料,在YouTube.网易云课堂.B站都能观看到相应的课程视频,接下来这一系列的博客我都将记录老师上课的笔记以及自己对 ...

  2. 秒懂HTTPS

    http和https HTTP:超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式.协作式和超媒体信息系统的应用层协议.HTTP是万维网的数 ...

  3. python数据结构之队列(一)

    队列概念 队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表. 队列是一种先进先出的(First In First Out)的线性表,简称FIFO.允许插入的一端为队尾,允许 ...

  4. 2017-9-13-Linux移植:bootloader烧写

    首先看一下Linux启动过程: Linux启动过程 刚开始最重要的是Bootloader的启动,Bootloader因你改改存放到哪?怎么执行?作用是啥? bootloader的烧写: 所谓烧写也就是 ...

  5. 2017-9-8-李明Linux:Linux应用与发展

    1965年,MIT.通用电气&贝尔实验室联合开发分时操作系统Multics,没有项目管理概念和追求目标过于庞大导致项目失败:1969年,参加过该项目的贝尔实验室成员肯 ·汤普森对自己开发的游戏 ...

  6. jquery 1.7.2源码解析(二)构造jquery对象

    构造jquery对象 jQuery对象是一个类数组对象. 一)构造函数jQuery() 构造函数的7种用法: 1.jQuery(selector [, context ]) 传入字符串参数:检查该字符 ...

  7. 潭州课堂25班:Ph201805201 django 项目 第二十九课 docker实例,文件下载前后台实现 (课堂笔记)

    docker 实例 :wq!保存退出 放入一个 html 文件 权限不够,加 sudo 查看本地仓库的 image 运行 docker -- name,后跟个运行名, -p 物理机端口映射到容器端口, ...

  8. equals()与hashCode()

    两个都可以用来判断两个对象是否相同一致. hashCode相同的不一定是同一个对象:hashCode不同的一定不是相同对象 equals相同的一定是相同对象,是绝对可靠的 既然equals这么可靠,那 ...

  9. DAG 上的动态规划(训练指南—大白书)

    有向无环图(DAG,Directed Acyclic Graph)上的动态规划是学习动态规划的基础.很多问题都可以转化为DAG上的最长路.最短路或路径计数问题. 一.矩形嵌套 题目描述:       ...

  10. 双向BFS—>NOIP2002 字串变换

    如果目标也已知的话,用双向BFS能很大提高速度 单向时,是 b^len的扩展. 双向的话,2*b^(len/2)  快了很多,特别是分支因子b较大时 至于实现上,网上有些做法是用两个队列,交替节点搜索 ...