🗒️std::forward 原理解析

type
status
date
slug
summary
tags
category
icon
password
💡
最近在阅读C++ Primter中关于模板的部分,发现网上一些博客对Forward的原理有不同的解读,翻阅了eel.is的相关draft,这里给出自己的理解。

从类型推导开始

推导 & 引用

在C++17之前,模板只支持函数的类型推导。而在推导规则当中,有很重要的一点:
对于模板参数 typename T 而言,只有参数类型为 T&& ,即对类型的右值引用,会导致类型推导为引用类型。
参照eel.is:
许多博客将对左值引用的推断解释为引用,是错误的。左值引用的推断只会推断出基本类型。即:
其中,f(a) 生成的模板实例当中,T为 int ,而非引用类型。
而当参数为右值引用时,推导结果如下:
  1. 传入左值,T 推导为左值引用
  1. 传入右值,T 推导为基本类型
只有参数为右值引用的时候,T才有可能被推断为左值引用!

引用折叠

通常来讲,我们无法使用引用来初始化引用。一个引用类型仅在 decltype 不会被视为引用的对象(别名)。
而现在,我们有了两种方式来使引用指向引用:
  • 别名,typedef or using
  • 右值引用参数模板参数的推断
折叠的规则如下:
  • 当且仅当 &&+&& 时,折叠为右值引用 &&。
  • 其他情况如 &+&,&&+&时,均折叠为左值引用。

完美转发?Why?

右值引用 & 解析

要理解为什么需要完美转发,需要理解右值引用的解析规则,或者说生命周期。
参考eel.is中,关于表达式解析的章节:
对于有名的(named)右值引用,将被当作左值引用对待。
这点非常重要,继续回忆函数传参的过程。C++ Primer中提到
Arguments are used to initialize parameters.
而我们的参数基本上都是有名(named)的,这就导致我们无法将右值引用显性的传递给子函数。如果我们直接传递右值引用变量,会被解析为左值引用传递。而左右值的语义是我们分辨move和copy的关键。
因此,我们需要一个方法,帮助我们在传递参数的过程中,保持参数的左右值语义。

来了,std::forward!

先欣赏源码:
其中,remove_reference 利用模板匹配的规则,帮助我们获得原始类型,具体细节这里不细讲。
假设我们有如下函数需要进行转发,也就是我们的场景:
为了便于区分,我们的模板使用X作为类型,而X的基本类型,我们简写为BaseX
回忆上文讲到的推测规则!

当传入右值时,X被推测为基础类型

此时,X = BaseX,会匹配到
则会保持右值引用的属性,因为返回类型为T&&,而我们传入的是X,此时X本身不带引用,则会返回一个右值引用(unnamed,无名),而无名的右值(或者说xvalue形式)引用会保持其右值的语义,完美转发了右值引用。

当传入左值时,X被推测为引用类型

此时,X = BaseX&,会匹配到
此时,T = BaseX&T&& 则会触发引用折叠,即 T&& = BaseX &&&,因此返回一个左值引用类型。

总结

本文总结了std::forward的相关原理,帮助大家理解右值引用对类型推断的作用,希望对大家有所帮助。相同的有类似std::move的源码,大家可以去看下是否能够理解。
 
上一篇
本站的搭建过程
下一篇
UDP数据包大小的讨论
Loading...