首页 > 其他分享 >条款28.理解引用折叠

条款28.理解引用折叠

时间:2023-04-27 16:22:23浏览次数:47  
标签:std Widget 右值 28 折叠 左值 引用 forward 条款

 

解引用折叠

以下面这个模板为例

template<typename T>

void func(T&& param);

  • 1
  • 2

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

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

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

 

Widget w; //变量(左值)

 

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

 

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

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

int x;

...

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

  • 1
  • 2
  • 3

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

template<typename T>

void func(T&& param);

 

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

  • 1
  • 2
  • 3
  • 4

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

void func(Widget& && param);

  • 1

引用的引用!

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

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

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

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

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

template<typename T>

void f(T&& fParam)

{

    ...

    someFunc(std::forward<T>(fParam)); //将fParam转发至someFunc

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

由于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);

}

  • 1
  • 2
  • 3
  • 4
  • 5

假设传递给函数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); }

  • 1
  • 2
  • 3

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

Widget& && forward(Widget& param)

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

  • 1
  • 2

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

Widget& forward(Widget& param)

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

  • 1
  • 2

如你所见,当左值实参被传递给函数模板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); }

  • 1
  • 2

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

Widget&& forward(Widget& param)

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

  • 1
  • 2

这里没有发生引用的引用,所以也就没有发生引用折叠,所以这也就已经是本次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);

}

  • 1
  • 2
  • 3
  • 4
  • 5

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

template<typename T>

void func(T&& param);

 

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

 

Widget w; //变量左值

 

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

 

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这一切都能以auto模仿。

下面这个声明:

auto&& w1 = w;

  • 1

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

Widget& && w1 = w;

  • 1

引用折叠后,又会变成

Widget& w1 = w;

  • 1

w1仍然是左值引用。

下述声明

auto&& w2 = widgetFactory();

  • 1

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

Widget&& w2 = widgetFactory();

  • 1

w2是右值引用。

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

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

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

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

template<typename T>

class Widget{

public:

    typename T&& RvalueRefToT;

    ...

};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

Widget<int&> w;

  • 1

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

typedef int& && RvalueRefToT;

  • 1

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

typedef int& RvalueRefToT;

  • 1

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

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

要点速记

  • 引用折叠会在四种语境中发生:模板实例化,auto类型生成,创建和运用typedef和别名声明,以及decltype。
  • 万能引用就是在类型推导过程会区分左值和右值,以及会发生引用折叠的语境中的右值引用。
  • 当编译器在引用折叠的语境下生成引用的引用时,结果会变成单个引用。如果原始的引用中有任一引用为左值引用,则结果为左值引用,否则,结果为右值引用。

 

https://blog.csdn.net/qq_36553387/article/details/116915635

标签:std,Widget,右值,28,折叠,左值,引用,forward,条款
From: https://www.cnblogs.com/im18620660608/p/17359276.html

相关文章

  • 【C++】引用、引用初始化、引用折叠规则
     引用引用就好像存储数据的一块内存区域(变量)的一个名字,定义引用就好像声明了一个变量名并把它绑定到已存在的变量上,变量名附带属性(包括但不限于类型、存储期),变量名附带的属性由声明变量名时使用的声明指定符决定。需要注意的是,用于声明左值引用的&和用于声明右值引用的&&不......
  • [ABC128D] equeue
    2023-01-14题目传送门翻译难度&重要性(1~10):题目来源AtCoder题目算法暴力,贪心解题思路由题意可以得出,数据只有\(n\leq50,k\leq100\)。所以,可以使用暴力,枚举从左右两边取的个数(只能从两边取),用一个数组记录下负数,去玩两边之后,将负数排个序,再用剩下的步骤,每次将最小的......
  • [ABC128E] Roadwork
    2023-01-14题目传送门翻译难度&重要性(1~10):题目来源AtCoder题目算法区间覆盖,线段树,双堆解题思路可以将问题转化为区间覆盖问题和单点查询问题。一共会有\(3\)种操作:\(1.\)插入一个区间。\(2.\)删除一个区间。\(3.\)查询一个区间。想到这我是想用双堆去做。......
  • SSL/TLS 受诫礼(BAR-MITZVAH)攻击漏洞(CVE-2015-2808) 修复方案
    详细描述SSL/TLS协议是一个被广泛使用的加密协议,BarMitzvah攻击实际上是利用了"不变性漏洞",这是RC4算法中的一个缺陷,它能够在某些情况下泄露SSL/TLS加密流量中的密文,从而将账户用户名密码,信用卡数据和其他敏感信息泄露给黑客。解决方法临时解决方法:服务器端(SSL/TLS)--------1......
  • GB/T28181-2022相对2016版“基于TCP协议的视音频媒体传输要求“规范解读和技术实现
    规范解读GB/T28181-2022和GB/T28181-2016规范,有这么一条“更改了附录D基于TCP协议的视音频媒体传输要求(见附录D,2016年版的附录L)。”。本文主要是针对GB/T28181-2022里面提到的“基于TCP协议的视音频媒体传输要求”做相应的接口适配,在此之前,我们先回顾下规范里面针对这部分......
  • OSD自然OUT之后无法再加入集群-v1-20210308_124828
    OSD自然OUT之后无法再加入集群企业云平台产品中心共享知识库Exportedon03/08/2021TableofContents问题描述4问题原因5解决方法6验证步骤6相关下载链接:OSD自然OUT之后无法再加入集群.pdf1--------这是一条华丽的分割线--------1https://iwiki.woa.com/dow......
  • 界面控件DevExtreme使用指南 - 折叠组件快速入门(二)
    DevExtreme拥有高性能的HTML5/JavaScript小部件集合,使您可以利用现代Web开发堆栈(包括React,Angular,ASP.NETCore,jQuery,Knockout等)构建交互式的Web应用程序,该套件附带功能齐全的数据网格、交互式图表小部件、数据编辑器等。请注意:在开始本教程之前,请确保DevExtreme已安装在Angula......
  • hihoCoder Challenge 28 异或问题 思维
    Givenasequencea[1..n],youneedtocalculatehowmanyintegersSsatisfythefollowingconditions:(1).0≤S<260(2).Foreveryiin[1,n-1],(a[i]xorS)≤(a[i+1]xorS)InputOnthefirstlinethereisonlyoneintegernOnthesecondlinethere......
  • Codeforces Round #285 (Div. 2) B. Misha and Changing Handles map 映射
    MishahackedtheCodeforcessite.Thenhedecidedtoletalltheuserschangetheirhandles.Ausercannowchangehishandleanynumberoftimes.Buteachnewhandlemustnotbeequaltoanyhandlethatisalreadyusedorthatwasusedatsomepoint.Mish......
  • Codeforces Round #285 (Div. 2) C. Misha and Forest
    Let’sdefineaforestasanon-directedacyclicgraph(alsowithoutloopsandparalleledges).OnedayMishaplayedwiththeforestconsistingofnvertices.Foreachvertexvfrom0ton - 1hewrotedowntwointegers,degreevandsv,werethefirstinte......