查阅标准文档以及effective c++作者文笔 真正搞懂万能引用和引用折叠以及完美转发

news2024/11/15 7:56:43

在解释任何东西以前 我都必须要强调 我们为什么需要这个东西 如果一个东西我们都是不需要的 那么我们解释他干嘛? 假定你彻底了解了一个东西 但是你并不知道你为什么需要他 他能解决什么问题 那你仅仅就只是背了一段理论性的东西 对于你本人的成长毫无用处 这里我们一次性讲懂万能引用 引用折叠以及完美转发三个知识点 如果任意一个知识点没看懂 请停下来好好看 切勿囫囵吞枣 不然你绝对是一知半解

网上一些文章属实误人子弟 看了让人恶心 

前置知识:

我假设你已经懂了最基本的概念 如左值 右值 移动语义 值类型和值类别区别  我强烈建议你先搞懂这些概念再读本文  如果分不清左值右值 这里贴出c++11标准文档原文来帮助你分清 别去搞啥xvalue 啥将亡值 你要是脑子还是正常的 那么以下三条规则就足够你分清左值和右值直到你入土

先来搞清楚 标准文档原文中的expression到底是啥 这里贴出cppreference的定义

 An expression is a sequence of operators and their operands, that specifies a computation.

这句话翻译成人话就是 表达式是一系列操作数和操作的集合 这个集合明确了计算过程

但是我这里要强调 操作可以是0个 但是操作数必须是1个及其以上 如下图

最基本的操作如+-*/ 再比如& [] ->这些都可以被称之为是操作

而操作数这不用多说了吧

int i=4;
&i;//&i为表达式
int arr[5]{};
i;//i为表达式
i+4;//i+4为表达式
4;//5为表达式
arr[4];//arr[4]为表达式
  • If you can take the address of an expression, the expression is an lvalue.
  • 如果你可以对表达式取地址 那么表达式是一个左值
  • If the type of an expression is an lvalue reference (e.g., T& or const T&, etc.), that expression is an lvalue. 
  • 如果表达式是一个左值引用,那么表达式是一个左值(但是这里注意啊 我这里自己给你们说 即便是右值引用 表达式仍旧是一个左值 因为可以被取地址) 
  • 作者提示 所有的左值引用都是左值 即便他是作为临时对象 他仍然是左值! 记住这一条 这会影响到我们后续对于完美转发的讲解!(这是c++的规则) 例子如下
  • int& func(int& i)
    {
    	cout << "func(int&)" << endl;
    	return i;
    }
    void func(int&& i)
    {
    	cout << "func(int&&)" << endl;
    }
    int main()
    {
    	int i = 4;
    	func(func(i));//func(i)的返回值为左值! 我再次提示
    }
  • Otherwise, the expression is an rvalue.  Conceptually (and typically also in fact), rvalues correspond to temporary objects, such as those returned from functions or created through implicit type conversions. Most literal values (e.g., 10 and 5.3) are also rvalues.
  • 除以上两条规则之外 表达式是一个右值  从概念上以及理论上都应是如此 比如从函数返回值返回的临时对象和隐式转换得来的临时对象以及大多数的字面量(1,3)   

好 更多的前置知识我已经不想再去讲解了 下面让我们来看看为什么我们需要万能引用(下面是作者自己的见解 如果有问题还请多见谅 因为我找了很多资料也确实没发现有什么地方必须要使用到万能引用才能解决问题的,他更多的是和模板类型自动推导来一起来最大程序复用代码的,方便大家编程)

大家都知道c++模板的意义是为了尽最大可能的去复用代码简化大家的使用 那么我们按照c++11以前的方式要去写一个既能接受右值引用 又要接受左值引用的模板 那么我们就不得行写两个版本了 两个版本书写方式如下

template<typename T>
void test(T& t)//左值引用版
{
	t++;
	return;
}
template<typename T>
void test(const T& t)//右值引用版
{
	t++;
	return;
}

那么在c++11就有没有办法能够直接一次性把两个全部搞定呢? 让编译器来自动给我们推导我们需要的类型是啥 而不是我们手动还要去写两遍模板 c++11便推出了万能引用 万能引用的方式如下

特别重要 特别重要 特别重要

标准文档原文:

If a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is a universal reference.

作者翻译之后说人话就是 当且仅当T&& T这个东西是模板参数的时候 并且一定要涉及到编译器模板类型自动推导!!!!   他才表示万能引用 不然他是个右值引用啊 

int&& i=4;//int&&为右值引用

template<typename T>

void test(T&& t);//T&&为万能引用

auto&& i=4;//涉及到编译器模板类型自动推导 并且有&& auto&&为万能引用

我们改造我们的函数为万能引用版 

template<typename T>
void test(T&& t)//万能引用版
{
	t++;
	return;
}

现在我们的test函数就既可以接受左值 也可以接受右值了

好了 到这里为止 我们对于万能引用的介绍就已经做完了 那么完美转发和引用折叠又是什么鬼玩意呢? 既然有这两个东西 说明万能引用肯定是遇到了一些问题的 不然不可能要推出完美转发和引用折叠 我们来看看他遇到了啥问题

 如下图所示 4为右值 我们本来想在模板里面去调用真正正确的函数 但是却调用到了左值引用的版本 这明显不是我们想要的结果 造成这个问题的最本质原因是啥呢?

超级重要 超级重要 超级重要

当我们传递4给test函数的时候 发生了模板类型自动推导 然后

T的类型被推断为了int T的类型被推断为了int T的类型被推断为了int T的类型被推断为了int

t的类型被推断为了int&&  t的类型被推断为了int&& t的类型被推断为了int&& 

T和t不是同一个类型 T和t不是同一个类型 T和t不是同一个类型 T和t不是同一个类型

因为非常重要 所以我不得不多强调几次 这里涉及到模板类型推导规则 这不是本文的重点 作者再次强调 至于他为什么要被推导为这个类型 详情可以参阅c++标准文档中对于模板类型推导规则的概述 总之你就记住 他被人为的规定为了强制推导为上述类型

那么问题就来了 当t的类型被推断为int&&的时候 我们都已经在上文说过了 只要能被取地址 那么他就是一个左值 这里明显右值引用是能够被取地址的 那么他就变成了一个左值 左值肯定就调用到func(int&)版本去了呗

那么当我们需要维持原来的值类别的时候 我们就需要讲我们的代码改为完美转发版本 如下所示

我们加上forward<T>(t) 表示完美转发 而完美转发的实现原理要由引用折叠来支撑

上文我们看到了 完美转发可以解决我们的问题 但是我们不禁好奇 他到底是怎样做到的呢?很简单 作者带你看源码forward的源码 打上断点 我们可以看到我们实际调用的forward版本

上文我们已经说到了T被推断为了int类型 那么在使用forward<T>时相当于是 forward<int> 那么下图中的_Ty就是int

我们可以看到返回值为_Ty&& 返回值也是一个万能引用 OK 对于返回值的讲解就完成了 

我们看到参数为 remove_reference_t<_Ty>& 就相当于remove_reference_t<int>& 那么我们不禁好奇 这个remove啥的又是啥 我们继续进去看

可以看到remove_referfence_t<int>就等于 remove_reference<int> 然后我们再往上找他的特化版本

 那么就很容易看出他的作用了 就如他的名字一般 可以擦除引用类型拿到实际类型 比如remove_reference_t<int&&>这个类型就相当于是int现在我们可以继续回到forward版本了

 经过上面的推导 那么这里的参数就变为了int& _Arg 那么不就是我们想要的左值引用版本嘛? 我们传入的是forward<T>(t) t是右值引用 但是本身是左值 这不就正好传递进去了嘛

对于参数的讲解就到这里了 这没有任何问题了

接下来最匪夷所思的一幕来了 可以看到 函数体仅仅只做了一个操作

static_cast<_Ty&&>

也就是说他仅仅只是给咋们的这个T类型加上了两个&& 那么返回值就为int&& 这刚好就是一个右值引用 并且我们也说过 函数的返回值除左值引用外均是右值 这不就完美的保留了咋们的值类别了嘛? 既传入的是右值 我转发出去以后 仍然是一个右值

对于左值来说 T类型会被推断为int& 经过这个static_cast后就会变成int& && 那么这三个引用号又是怎样转发的呢

接下来就引入了我们的引用折叠规则 在编译器眼中是可以存在两个以上的引用号的 而用户眼中则不可以 这是什么意思呢 例子如下 可以明确看到我们用户不允许使用&&&三个引用号 这里贴上标准文档中的解释

The true core of the issue is that some constructs in C++11 give rise to references to references, and references to references are not permitted in C++. If source code explicitly contains a reference to a reference, the code is invalid:

Widget w1;

Widget& & w2 = w1; // error! No such thing as “reference to reference”

There are cases, however, where references to references arise as a result of type manipulations that take place during compilation, and in such cases, rejecting the code would be problematic. We know this from experience with the initial standard for C++, i.e., C++98/C++03.

作者翻译成人话给你们说就是 用户层不允许使用引用的引用 而编译阶段是允许存在的引用的引用的  

 所以 int& &&这种类型在编译器经过引用折叠就变成了普通的int& 引用折叠的规则如下:

看到网上很多文章在哪里说 啥int&& &&->int&& 之类的列一大堆引用折叠的规则 但是标准文档说的很清楚了 只有两条引用折叠规则

标准文档中对于引用折叠的描述如下

There are only two reference-collapsing rules:

  • An rvalue reference to an rvalue reference becomes (“collapses into”) an rvalue reference.
  • All other references to references (i.e., all combinations involving an lvalue reference) collapse into an lvalue reference.

 作者翻译成人话就是

只有T&& &&会被折叠为右值引用 其他全部为左值引用 仅此而已

 经过我们对于引用折叠的描述 那么int& &&就被折叠为了普通的左值引用 左值引用作为返回值 仍然是左值 那么他就正确的调用了我们想要的版本

 

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

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

相关文章

sqli-labs/Less-58

这一关只有五次机会了 哎怎么办啊 那就只能找出每轮的共同点 这一关肯定不能一轮就完成所有的操作 至少得分个两轮进行操作才可以 前一轮进行注入类型的获取 后一轮进行各种爆破操作 分配好了 首先去判断一下注入类型是否属于数字型注入 输入如下 id1 and 12 回显如下 不属于…

Web大学生网页作业成品 基于HTML+CSS+JavaScript---个人介绍5页 带视频 带报告

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | ‍个人博客网站 | ‍个人主页介绍 | 个人简介 | 个人博客设计制作 | 等网站的设计与制作 | 大学生个人HTML网页设计作品 | HTML期末大学生网页设计作业…

大规模 Spring Cloud 微服务无损上下线探索与实践

作者&#xff1a;十眠 “从一次常见的发布说起&#xff0c;在云上某个系统应用发布时&#xff0c;重启阶段会导致较大数量的 OpenAPI、上游业务的请求响应时间明显增加甚至超时失败。随着业务的发展&#xff0c;用户数和调用数越来越多&#xff0c;该系统又一直保持一周发布二…

CAD特殊符号,你不一定会!!!

在CAD软件中&#xff0c;有时候会输入一些特殊的符号。比如在标明高低差的时候会输入“”号&#xff0c;在标明管子或者钢筋的直径为输入直径符号“”&#xff0c;为了标明角度值需要输入符号“”&#xff0c;那么这些符号怎么快速的绘制出来呢&#xff1f;我们一起用CAD梦想画…

专利解析|多维建模结合AI识别商品特征的方法

企业采购数字化转型的背景 国家“十四五”规划纲要提出要推进产业数字化转型&#xff0c;在供给侧结构性改革大背景下&#xff0c;国家出台了《企业数字化采购实施指南》&#xff0c;大大促进了企业采购电商化的发展。企业电商化采购能提高企业的采购效率、加快物流速度、降低…

m基于QPSK调制解调的无线图像传输matlab仿真,包括扩频解扩均衡等模块

目录 1.算法描述 2.仿真效果预览 3.MATLAB部分代码预览 4.完整MATLAB程序 1.算法描述 软件无线电在无线通信领域被称为是自模拟通信过渡到数字通信之后的又一次革命&#xff0c;在军用和民用方面都有着广阔的应用。它是一种新的无线通信技术&#xff0c;基于通用的可编程的…

【JAVA高级】——封装JDBC中的DaoUtils工具类(Object类型方法)

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;JAVA开发者…

【文献整理】基于深度强化学习的知识图谱推理研究

目录DeepPath背景Core贡献几个要点&#xff1a;Training pipeline结论DIVINE背景Core贡献预备知识DIVINE推理过程模型文献整理基于综述论文&#xff1a;基于深度强化学习的知识推理研究进展综述_宋浩楠&#xff0c;赵刚&#xff0c;孙若莹 文中对知识图谱推理进行如下分类&…

SpringSecurity(十七)---OAuth2的运行机制(下)-实现一个简单的单点登录应用程序

一、前言 本章实现第一个使用带有Spring Boot和Spring Security 的OAuth2框架的应用程序。这个示例将展示如何将OAuth2应用到Spring Security中&#xff0c;并阐释你需要了解的一些接口的内容。顾名思义&#xff0c;单点登录&#xff08;SSO&#xff09;应用程序是通过授权服务…

如何使用一台电脑远程控制多台电脑

如今&#xff0c;远程控制软件已经广泛应用于我们的日常生活中。我们使用远程桌面软件远程控制另一台电脑来完成我们的工作和学习。在某些情况下&#xff0c;我们可能还需要同时远程控制多台电脑。例如&#xff1a; 您是一名培训师&#xff0c;正在寻找远程访问软件来同时远程…

[激光原理与应用-15]:《激光原理与技术》-1- 什么是激光,激光概述

目录 第1章 什么是激光 1.1 什么是激光 1.2激光在生活中应用 第2章 激光的特点 2.1 方向性好&#xff08;平行性、直线性&#xff09; 2.2 单色性好&#xff08;颜色纯度高&#xff09; 2.3 相干性比太阳光好 2.4 亮度高 2.5 能量极大 第3章 光产生的方式与核心概念 …

又爆冷了啦,日本半场逆转德国,怎么利用共享经济搅乱世界杯格局

近日世界杯热点逐渐升高&#xff0c;在23号晚上亚洲劲旅日本以2-1逆转多次捧得大力神杯的德国队&#xff0c;此前德国还从未输过日本队&#xff0c;因此德国再次吃到闭门羹&#xff0c;爆出了本届世界杯开赛以来既阿根廷惨败的又一大冷门。赛后&#xff0c;日本全国人民共同庆祝…

Web(二)html5基础-超链接的应用(知识训练和编程训练)

web知识训练_html5_超链接的应用 web编程训练_html5_超链接的应用 第1关_创建热字超链接 编程要求 在右侧编辑器中的Begin - End区域内补充代码&#xff0c;创建热字超链接&#xff0c;具体要求是&#xff1a; 1.链源文字为“听音乐找酷我”。 2.链宿地址为“https://www.ku…

FPGA——多路选择器实现按键控制LED灯的亮灭

文章目录前言一、多路选择器二、绘制模块框图及波形图三、Verilog HDL代码及测试代码四、创建工程五、仿真六、上板验证1、分配引脚2、烧录七、效果演示八、总结前言 软件&#xff1a;Quartus Prime Standard 18.0仿真软件&#xff1a;modelsim 10.5代码编写软件&#xff1a;V…

【虹科新品】 HK-MR430330绝对式光纤编码器介绍合集(下)

HK-MR430系列ZapFREE光纤位置传感器是一款外形小巧、具有13位单圈分辨率的旋转位置传感器。MR430设计新颖&#xff0c;开发了新的应用和OEM产品功能&#xff0c;这在以前的电子传感器是无法实现的。该传感器100%无源&#xff0c;不受EMI、RFI、微波和磁场的影响。创新型全绝缘设…

我参加NVIDIA Sky Hackathon 后端修改

文件架构 前面两个分别是执行语音识别和图片识别的代码templates 存放的是网页的模板&#xff0c; 前端将文件写在这里即可uploads 存放的是上传至后台的文件server.ipynb 用于启动 flash 服务器app.py 内是用 flash 写的 Python 后端install_tools.sh 是用于安装相关工具的 sh…

(十四)Spring之回顾代理模式

文章目录回顾代理模式动态代理常用技术CGLIB动态代理技术上一篇&#xff1a;&#xff08;十三&#xff09;Spring之JdbcTemplate 回顾代理模式 参考&#xff1a;代理模式Proxy Pattern 不用JDK的动态代理&#xff0c;手写JDK动态代理 动态代理常用技术 在程序运行阶段&…

CentOS 7 手动安装OpenStack

官网文档 因为之前已经在 Ubuntu 20.04 下完成了 Ubuntu 20.04 手动安装OpenStack &#xff0c;最后&#xff0c;想要学习 OVN 的时候&#xff0c;发现 ubuntu 上的 OVN 安装很复杂&#xff0c;没有 TripleO/RDO based deployments &#xff0c;所以&#xff0c;又在 CentOS 7…

项目开源!基于PaddleDetection打造实时人体姿态检测的多关节控制皮影机器人

本文已在【飞桨PaddlePaddle】公众号平台发布&#xff0c;详情请戳链接&#xff1a;项目开源&#xff01;基于PaddleDetection打造实时人体姿态检测的多关节控制皮影机器人 皮影戏是一种以兽皮或纸板做成的人物剪影以表演故事的民间戏剧&#xff0c;皮影一般由头、躯干&#x…

2003-2019年各省市场分割指数全步骤数据+最终结果

2003-2019年市场分割指数 1、时间&#xff1a;2003-2019年 2、数据包含&#xff1a;31各省份市场分割指数全步骤数据和最终结果 3、具体内容&#xff1a;市场分割指数差分形式相对价格、市场分割指数去均值、市场分割指数方差、市场分割指数最终结果。 4、指标说明&#xf…