所谓型别推导,指的是我们在为变量赋予类型时不必再显式声明,编译器可以根据代码来自动推导类型。C++11中有两种型别推导的场景:模板和auto。下面我们来一一解析。

模板的型别推导

模板在C++中的应用可以参考我的这篇文章:https://www.cnblogs.com/wickedpriest/p/6123113.html。

在这里我们为了简化问题,我们定义进行模板型别推导的形式为:

template<typename **T**>
void f(**ParamType** parm);

调用的时候的格式为:

f(**expr**);

这是一个标准的函数模板,类模板以及其他形式都可以参考这个。

在编译期,编译器会根据expr来推导T和ParamType的型别。需要注意的是,T和ParamType的型别有可能是不一样的:例如T是int,而ParamType是const int&。T型别的推导结果,不仅依赖于expr,也依赖于ParamType,具体来说分为三种情况:

一、ParamType是个指针或引用,但不是个万能引用

这种情况是最简单的,型别推导会这么运作:

1.若expr具有引用型别,先将引用部分忽略

2.对expr的型别和ParamType的型别执行模式匹配,来决定T的型别

举个例子,我们的模式如下:

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

声明变量如下:

int x = 27;         // x的型别为int
const int cx = x; // cx的型别为 const int
const int &rx = x; // rx的型别为const int &

那么以下调用的型别推导为:

f(x);     //T的型别是int,param的型别是int &
f(cx); //T的型别是cosnt int,param的型别是const int&
f(rx); //T的型别是const int,param的型别是const int&

请注意对rx的推导,如前面所说,expr的型别为const int&,ParamType又是引用,那么进行型别推导时首先会忽略掉expr的引用部分,然后再根据T和ParamType进行推导。

上面显示的都是左值引用形参,如果形参是右值引用也是一样的。

二、ParamType是个万能引用

关于万能应用的解析会在之后的文章中讲解,在这里我们先给出万能引用的形式:

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

在这种形式下,型别推导规则如下:

1.如果expr是个左值,T和ParamType都会被推导为左值引用(没错你没看错,虽然ParamType是个右值引用,但最终确实是被推导为了左值引用)。

2.如果expr是个右值,则应用上面情况一中的规则。

再次举个例子:

int x = 27;
const int cx = x;
cosnt int rx& =x;
f(x); //x是个左值,因此T和ParamType的型别为int&
f(cx); //x是个左值,因此T和ParamType的型别为const int&
f(rx); //rx是个左值,因此T和ParamType的型别为const int&
f(27); //27是个右值,因此T的型别为int,ParamType的型别为int&&

关于为什么会这样,我会在之后介绍万能引用的文章中予以说明。

三、ParamType既不是指针也不是引用

这种情况也非常简单,那就是完全地按值传递了,const和引用都会被省略掉

template<typename T>
void f(T param); f(x); //T和ParamType的型别都为int
f(cx); //T和ParamType的型别都为int
f(rx); //T和ParamType的型别都为int

当传递的类型是一个指针时,有一种特殊情况,如下所示:

const char* const x = "123456";
f(x); // ParamType的类型是const char*

x的类型为const char* const,其中第一个const指的是该指针指向的内容不可修改,第二个const值得是该指针本身的值不可修改。进行型别推导时,会取消掉第二个const,第一个const则不能取消(一旦取消,则x指向的内容就可以更改了)。

一种特殊情况:数组实参

接下来还有一种特殊情况,那就是传递了一个数组。一般来说,讲一个数组按值传递给函数模板,形参将会被推导为指针型别:

template<typename T>
void f(T param); const char name[] = "6666";
f(name); //T的型别为const char*

但神奇的是,尽管函数无法声明真正的数组型别的形参,它们却能够将形参声明成数组的引用!举个例子,如果我们这样修改f:

template<typename T>
void f(T& param); f(name);

此时T会被推导为数组型别:const char[5]!而f的形参(param)则会被推导为数组类型的引用:const char(&)[5]。

我们可以利用这种特性来推断数组的大小:

template<typename T, std::size_t N>
constexpr std::size_t arraySize(T(&)[N])noexcept
{
return N;
} int nums[] = {1,2,3,4,5};
int vals[arraySize(nums)];

惊不惊喜,意不意外?

auto的型别推导

auto实际上在大部分情况下可以看做是模板的型别推导,除了一种特殊情况。我们先看看auto如何与模板的型别推导对应的:

auto x = 27;
const auto cx = x;
const auto& rx = x;

上面三种情况完全可以用模板推导表达:

template<typename T>
void func_for_x(T param);
func_for_x(27); template<typename T>
void func_for_cx(const T param);
func_for_x(x); template<typename T>
void func_for_rx(const T& param);
func_for_x(x);

那么对应模板推导的三种情况如下:

auto x = 27;         //情况3(x既不是指针也不是引用)
const auto cx = x; //情况3(x既不是指针也不是引用)
const auto& rx = x; //情况1(rx是个引用,但不是万能引用)

情况2同理:

auto && uref1 = x;  //x的型别是int,且是左值,所以uref1的型别是int&
auto && uref2 = cx; //同上,uref2的型别是const int&
auto && uref3 = 27; //27是右值,因此uref3的型别是int&&

数组也一样:

const char name[] ="6666";
auto arr1 = name; //arr1的型别是const char*
auto& arr2 = name; //arr2的型别是const char(&)[5]

你看,一模一样。

那么特殊情况是什么呢?就是当我们用初始化表达式来初始化一个类型的时候:

int x{2};
auto x1 = {2};

上面的代码采用大括号来初始化对象,x的型别为int,而auto则不会将x1推导为int,而是std::initializer_list!这一点一定要注意。

如果采用模板型别推导,传入一个初始化表达式,则程序会直接报错:

template<typename T>
void f(T param);
f({1,2,3}); //程序无法推导T的类型,直接报错!

除非你手动指定param的类型为std::initializer_list:

template<typename T>
void f(std::initializer_list<T> param);

除了这一条以外,auto的推导规则是和模板一模一样的。

深入理解C++的型别推导的更多相关文章

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

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

  2. Effective Modern C++ 条款2:理解auto型别推导

    在条款1中,我们已经了解了有关模板型别的推导的一切必要知识,那么也就意味着基本上了解了auto型别推导的一切必要知识. 因为,除了一个奇妙的例外情况,auto型别推导就是模板型别推导.尽管和模板型别推 ...

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

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

  4. Effective Modern C++ 条款4:掌握查看型别推导结果的方法

    采用何种工具来查看型别推导结果,取决于你在软件开发过程的哪个阶段需要该信息.主要研究三个可能的阶段:撰写代码阶段.编译阶段.运行时阶段. IDE编译器 IDE中的代码编译器通常会在你将鼠标指针选停止某 ...

  5. 《Effective Modern C++》翻译--条款2: 理解auto自己主动类型推导

    条款2: 理解auto自己主动类型推导 假设你已经读过条款1关于模板类型推导的内容,那么你差点儿已经知道了关于auto类型推导的所有. 至于为什么auto类型推导就是模板类型推导仅仅有一个地方感到好奇 ...

  6. Java Comparator的范型类型推导问题

    问题 在项目中,有一处地方需要对日期区间进行排序 我需要以日期区间的开始日为第一优先级,结束日为第二优先级进行排序 代码 我当时写的代码如下: List<Pair<LocalDate, L ...

  7. c++:参数型别的推导

    STL源码剖析--侯捷 总结 尽管现在的很多语言支持参数类型的判别,但是c/c++并不支持这一特性. 但是我们可以通过一些技巧使得c++具有自动判别参数类型的特性. 模板 我们都知道在模板类和模板函数 ...

  8. python 3列表推导式的的一点理解!

    python 3列表推导式的的一点理解! Python的列表推导式对于新手来说一般都难以理解,简单看个例子: [x * x for x in range(1,100)] 上面是一个很简单的列表推导式, ...

  9. 《编译原理》-用例题理解-自底向上的语法分析,FIRSTVT,LASTVT集

    <编译原理>-用例题理解-自底向上的语法分析,FIRSTVT,LASTVT集 上一篇:编译原理-用例题理解-自顶向下语法分析及 FIRST,FOLLOW,SELECT集,LL(1)文法 本 ...

  10. auto类型推导

    引言 auto : 类型推导. 在使用c++的时候会经常使用, 就像在考虑STL时迭代器类型, 写模板的时候使用auto能少写代码, 也能帮助我们避免一些隐患的细节. auto初始化 使用auto型别 ...

随机推荐

  1. OpenCv:边缘处理&Canny边缘检测

    卷积边缘: 一.在进行卷积的时候,对于原图像(1,1)的位置,左.上方没有足够的像素做卷积. 二.通常,在卷积之前,在原图增加边缘像素,比如(3*3的,增加一个像素) 三.增加像素值得处理方法,通常有 ...

  2. GitBook的使用备忘

    GitBook环境搭建 npm install -g gitbook-cli # 新建目录,如helloworld cd helloworld # 执行此语句,需等待一段时间 gitbook init ...

  3. 5.docker安装redis

    下载redis镜像 不讲那么细了,可以参考docker安装mysql的介绍 docker pull redis 不加冒号和版本表示下载最新版本的 镜像下载完后可以数据 docker images 命令 ...

  4. Hide-and-Seek: Forcing a Network to be Meticulous for Weakly-Supervised Object and Action Localization概述

    0.前言 相关资料: paper 网站 论文解读(知乎,CSDN) 论文基本信息: 领域:弱监督动作定位 发表时间:ICCV2017 1.针对的问题 大多数网络只识别图像最具有鉴别力的部分,不是所有相 ...

  5. mongoDB日常操作02

    db.TABLE_NAME.find({<query>})//普通查询db.TABLE_NAME.find({<query>},{'_id':0,'f1':1,'f2':1}) ...

  6. IDEA打包普通java项目并用java命令运行

    IDEA下打包为jar包,普通java项目(非web项目) 效果是将第三方jar包放到一个文件夹中(如lib),这样看起来清晰一些.如下图这种: 1.项目结构. 1.关键:modules 在<o ...

  7. 【2020NOI.AC省选模拟#6】A. zyb的监控计划

    题目链接 原题解: 考虑我们需要的信息:子树里最浅的一个能向上的点是谁?子树里最深的一个没被覆盖的深度是多少? 我们记录一下$f_{i,a,b}$表示上面两个信息为$a$和$b$的时候,最少要花费的代 ...

  8. SpringBoot - 参数校验、统一异常、统一响应

    转载自: https://blog.csdn.net/chaitoudaren/article/details/105610962 前言 本篇主要要介绍的就是controller层的处理,一个完整的后 ...

  9. Linux非正式学习随笔(1)

    11.5进linux学的第一件事,找个中文输入法.Linux是一套免费的类unix操作系统GPL:gnu通用公共许可证.托马斯斯托曼提出gnu计划,自由软件思想的一个协议.Linux诞生1991年10 ...

  10. MongoDB 副本集(Replica Set)

    副本集(Replica Set) 副本集概念 此集群拥有一个主节点(Master)和多个从节点(Slave),与主从复制模式类似,但是副本集与主从复制的区别在于:当集群中主节点发生故障时,副本集可以自 ...