条款28.理解引用折叠

2023-11-15

理解引用折叠

以下面这个模板为例

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

模板形参T的推导类型中,会把传给param的实参是左值还是右值的信息给编码进去。

编码机制是直截了当的:如果传递的实参是个左值,T的推导结果就是个左值引用类型;如果传递的实参是个右值,T的推导结果就是非引用类型

Widget widgetFactory();	//返回右值的函数

Widget w;		//变量(左值)

func(w);	//调用func并传入左值:T的推导结果类型为Widget&

func(WidgetFactory())	//调用func并传入右值:T的推导结果类型为Widget

两个对func的调用,传递的实参类型都为Widget。不同之处仅在于,一个是左值另一个是右值,而这个不同之处却导致了针对模板形参T得出了不同的类型推导结果。

在C++中,引用的引用是非法的

int x;
...
auto&& rx = x;	//错误,不可以声明引用的引用

当左值被传递给接受万能引用的函数模板时会发生下面的状况

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

func(w);		//调用func并传入左值:T的推导结果类型为Widget&

如果把T的推导结果类型(即Widget&)代码实例化模板。

void func(Widget& && param);

引用的引用!

引用折叠,我们被禁止声明引用的引用,但编译器却可以在特殊的语境中产生引用的引用,模板实例化就是这样的语境之一。

有两种引用(左值和右值),所以就有四种可能的引用——引用的组合(左值-左值,左值-右值,右值-左值,右值-右值)。如果引用的引用出现在允许的语境,该双重引用会折叠成单个引用,规则如下:

如果任一引用为左值引用,则结果为左值引用。否则(即两个皆为右值引用),结果为右值引用

在上述例子中,将推导结果类型Widget&带入函数模板func后,产生了一个指向左值引用的右值引用,然后,根据引用折叠规则,结果是个左值引用。

引用折叠是使std::forward得以运行的关键

template<typename T>
void f(T&& fParam)
{
    ...
    someFunc(std::forward<T>(fParam));		//将fParam转发至someFunc
}

由于fParam是个万能引用,我们就知道,传递给f的实参是左值还是右值的信息会被编码到类型形参T中。std::forward的任务是,当且仅当编码T中的信息表明传递给实参是个右值,即T的推导结果类型是个非引用类型时,对fParam(左值)实施到右值的强制类型转换

这里是std::forward的一种能够完成任务的实现。

template<typename T>
T&& forward(typename remove_reference<T>::type& param)
{
    return static_cast<T&&>(param);
}

假设传递给函数f的实参的类型是个左值Widget,则T会被推导为Widget&类型,然后对std::forward的调用就会实例化为std::forward<Widget&>,而将Widget&插入std::forward的实现就会产生如下结果。

Widget& && forward(typename 
                  remove_reference<Widget&>::type& param)
{ return static_cast<Widget& &&>(param); }

由于类型特征remove_reference<Widget&>::type的产生结果是Widget类型,所以std::forward又变换成了下面的结果。

Widget& && forward(Widget& param)
{ return static_cast<Widget& &&>(param); }

引用折叠同样在返回值和强制类型转换的语境中得到了实施,导致实际调用结果是这样的终极版本std::forward

Widget& forward(Widget& param)
{ return static_cast<Widget&>(param); }

如你所见,当左值实参被传递给函数模板f时,std::forward实例化结果是:接受一个左值引用,并返回一个左值引用,而std::forward内部的强制类型转换未做任何事情。因为param的类型已经是Widget&,所以再要把它强制转换成Widget&类型不会产生什么效果。综上,被传递给std::forward的左值实参会返回一个左值引用。根据定义,左值引用是左值,所以传递给std::forward会导致返回一个左值。

再假设传递给f的实参是右值Widget类型,在此情况下,f的类型形参T的推导结果是个光秃秃的Widget。因此,f内部的std::forward就成了std::forward<Widget>。在std::forward的实现中,在T之处用Widget代入,就得出下面的代码:

Widget&& forward(typename remove_reference<Widget>::type& param)
{ return static_cast<Widget&&>(param); }

针对非引用Widget类型实施std::remove_reference会产生和起始类型相同的结果Widget,所以std::forward又变成了这样:

Widget&& forward(Widget& param)
{ return static_cast<Widget&&>(param); }

这里没有发生引用的引用,所以也就没有发生引用折叠,所以这也就已经是本次std::forward调用的最终实例化版本了。

由函数返回的右值引用是定义为右值的,所以在此情况下,std::forward会把f的形参fParam(左值)转换成右值。最终的结果是,传递给函数f的右值实参会作为右值转发给somefunc函数。

C++14中有了std::remove_reference_t,从而std::forward的实现得以变得更加简明扼要。

template<typename T>
T&& forward(remove_reference_t<T>& param)
{
    return static_cast<T&&>(param);
}

引用折叠会出现的语境有四种。第一种,模板实例化;第二种,是auto变量的类型生成

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

Widget widgetFactory();		//返回右值的函数

Widget w;			//变量左值

func(w);	//调用func并传入左值:T的推导结果类型为Widget&

func(WidgetFactory())	//调用func并传入右值:T的推导结果类型为Widget

这一切都能以auto模仿。

下面这个声明:

auto&& w1 = w;

初始化w1的是个左值,因此auto的类型推导结果为Widget&。在w1声明中以Widget&带入auto,就产生了以下这段代码

Widget& && w1 = w;

引用折叠后,又会变成

Widget& w1 = w;

w1仍然是左值引用。

下述声明

auto&& w2 = widgetFactory();

以右值初始化w2auto的类型推导结果为非引用类型Widget。将Widget带入auto就得到

Widget&& w2 = widgetFactory();

w2是右值引用。

万能引用并非是一种新的引用类型,其实它就是满足了下面两个条件的语境中的右值引用

  • 类型推导的过程会区别左值和右值。T类型的左值推导结果为T&,而T类型的右值推导结果为T
  • 会发生引用折叠

发生引用折叠的第三种语境是生成和使用typedef和别名声明

如果在typedef的创建或者评估求值的过程中出现了引用的引用,引用折叠就会出手消灭它

template<typename T>
class Widget{
public:
    typename T&& RvalueRefToT;
    ...
};

假设我们以左值类型来实例化该Widget

Widget<int&> w;

Widget中以int&代入T的位置,则得到如下的typedef

typedef int& && RvalueRefToT;

引用折叠又将上述语句化简得到

typedef int& RvalueRefToT;

这个结果显然表明,我们为typedef选择的名字也许有些名不副实:当以左值引用类型实例化Widget时,RvalueRefToT其实成了左值引用的typedef。

最后一种发生引用折叠的语境在于decltype的运用中,如果在分析一个涉及decltype的类型过程中出现了引用的引用,则引用折叠也会介入并消灭它。

要点速记

  • 引用折叠会在四种语境中发生:模板实例化,auto类型生成,创建和运用typedef和别名声明,以及decltype
  • 万能引用就是在类型推导过程会区分左值和右值,以及会发生引用折叠的语境中的右值引用。
  • 当编译器在引用折叠的语境下生成引用的引用时,结果会变成单个引用。如果原始的引用中有任一引用为左值引用,则结果为左值引用,否则,结果为右值引用。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

条款28.理解引用折叠 的相关文章

随机推荐

  • matinal:SAP ABAP FB04清账 POSTING_INTERFACE_CLEARING

    上代码 REPORT ztest INTERNAL TABLE DECLARATION DATA it blntab TYPE TABLE OF blntab WITH HEADER LINE it ftclear TYPE TABLE O
  • JAVA内存管理常识

    大多数 JVM 将内存区域划分为 Method Area Non Heap 方法区 Heap 堆 Program Counter Register 程序计数器 VM Stack 虚拟机栈 也有翻译成JAVA 方法栈的 Native Meth
  • flex布局可能碰到的坑1

    flex布局非常好用 但在开发过程中可能会碰到的一些坑 1 内容超出容器 大致情况是 在一个设置了display flex布局的大容器A中并排放置两个子容器 并且子容器设置flex 1 子容器中都有一个元素包含一段文本 这段文本设置了不换行
  • 服务器终端性能测试之GPU burn压力测试

    GPU burn 测试GPU 1 下载软件 https github com wilicc gpu burn wget https codeload github com wilicc gpu burn zip master 2 解压缩 u
  • win10家庭版启用远程桌面

    此电脑右键属性 gt 远程设置 gt 允许远程协助连接这台计算机 勾选 下载RDP Wrapper 地址 https github com stascorp rdpwrap releases 解压后点击RDPCheck exe 如显示无法连
  • VS2019使用以及UE4的代码调试

    1 会进行代码调试 Ctrl Shift B 编译代码 Ctrl f5 运行 不调试 f5 调试 shift f5 停止调试 f11 逐步执行 f9 切换断点 对于UE4的工程代码的调试仍然需要学习与总结 2 会进行函数查找 不借助番茄插件
  • lattice 包的用法

    1 library lattice 加载包 d lt data frame x seq 0 14 y seq 1 15 z rep c a b c times 5 xyplot y x data d xy的散点图 xyplot y x z
  • 关于贷后的8个专业名词解析

    一 DPD day past due DPD的意思是逾期天数 指的是逾期用户在最早违期日期至目前日期的时间间隔 贷后催收时需要计算用户的逾期天数 并根据逾期的情况采用不同的催收手段 二 Mn M1 Mn的意思是逾期的期数 比如M1表示逾期一
  • 《Autodesk Revit二次开发基础教程》书籍终于上架了

    由Autodesk中国研究院Revit开发团队的几位同事一起编撰的 Autodesk Revit二次开发基础教程 于今天在天猫同济大学出版社旗舰店正式上架 购买链接在这里 https detail tmall com item htm u
  • TensorFlow训练模型的过程中打开tensorboard

    在训练的过程中 想通过tensorboard实时观察训练损失和验证集准确率 一直出错 打开tensorboard后在浏览器查看 然后训练就停止了 提示信息如下 File D ProgramData PycharmProjects tf le
  • Spring源码分析(一):Spring底层核心原理解析

    本节只讲结论 不做验证 后面会专门拉代码讲解验证 Spring的核心是IOC和AOP 大概有这么几个核心知识点 Bean的生命周期底层原理 依赖注入底层原理 初始化底层原理 推断构造方法底层原理 AOP底层原理 Spring事务底层原理 S
  • 大陆医生谈收入

    官网 ZY123 com 中医123 本人今年45岁 84年大本 87年研究生毕业 很正规的医学院校毕业 随后分到中部一家大型医院干了4年临床 91年先到欧洲的实验室混了几年 后来到临床上干了2年后回来了 也算 海龟 吧 先在广东的两家医院
  • 【基础教学】UiBot的下载、安装与使用

    鉴于很多小伙伴 可能刚刚关注UiBot 对这个平台还不是很了解 我们准备系统的讲解UiBot的相关操作 方便您对UiBot的认识与使用 目录 1 UiBot软件简介 2 UiBot能为您做什么 3 系统环境及配置要求 4 下载与安装 5 注
  • 堆排序(几个重点)

    https blog csdn net touch 2011 article details 6767673 几个重点 大小顶堆虽然逻辑形式是完全二叉树 但实际是以数组的形式存储 最后一个非叶子节点 最后一个有孩子的节点 的位置是 n 2
  • linux 获取进程输出流,linux后台进程与标准输出

    一 遇到问题 笔者在测试阶段 把服务拉到服务器上 部署之后 启动服务 但是没有启动成功 也没有报错信息 二 先理解一些概念 1 黑洞 dev null 这个就是黑洞 这是一个文件 这个文件是一个 只写 的文件 从里面读不出信息 为什么要使用
  • Elasticsearch 查询和聚合查询:基本语法和统计数量

    摘要 Elasticsearch是一个强大的分布式搜索和分析引擎 提供了丰富的查询和聚合功能 本文将介绍Elasticsearch的基本查询语法 包括预发查询和聚合查询 以及如何使用聚合功能统计数量 引言 Elasticsearch是一种开
  • C++实现查找字符串中的数字,并输出

    例如输入 dsafjoi3425sfsdjl5435asfkl 3400输出为 3425 5434 3400 include
  • Stata如何快速安装外部命令

    Stata如何快速安装外部命令 来自微信公众号 TidyFridy 1 之前在安装Stata外部命令时 访问外网速度很慢 安装SSC外部命令没有成功 出现过stacktrace not available 的提示 解决办法 Stata的安装
  • 数据结构课程设计-五子棋

    1 题目描述 五子棋的游戏规则是两人对弈 使用黑白两色棋子 轮流下在棋盘上 当一方先在横线 竖线 斜对角线方向形成五子连线 则取得胜利 2 设计要求 在内存中 设计数据结构存储游戏需要的数据 满足五子棋游戏的游戏规则 实现简单的人机对战功能
  • 条款28.理解引用折叠

    理解引用折叠 以下面这个模板为例 template