【C++】std::forward个人理解,模板函数std::forward<T>,“T”代表什么意思

std::forward 个人理解

模板函数里面的std::forward<T>,"T"代表什么意思

template<typename T>
Print(T&& param)
{
    // 打印 t 的类型
}

template<typename T>
foo(T&& param)
{
    Print(std::forward<T>(param));
}

int main()
{
    int a = 10;  // a 是左值
    int& lrefa = a;  // lrefa 是左值引用
    int&& rrefa = 10;  // rrefa 是右值引用
    foo(a);
    foo(lrefa);
    foo(std::forward<decltype(rrefa)>(rrefa));  
		// rrefa是右值引用类型的左值,所以要将右值的类型传入进foo,这样param才是右值
		// 如果是foo(rrefa),就相当于传入了一个左值

}
/*
Output:

传入之前的类型:
a type:     int
lrefa type: int & __ptr64
rrefa type: int && __ptr64

传入左值:
in foo, T type:       int & __ptr64
in foo, param type:   int & __ptr64
in Print, T type:     int & __ptr64
in Print, param type: int & __ptr64

传入左值引用:
in foo, T type:       int & __ptr64
in foo, param type:   int & __ptr64
in Print, T type:     int & __ptr64
in Print, param type: int & __ptr64

传入右值引用:
in foo, T type:       int
in foo, param type:   int && __ptr64
in Print, T type:     int
in Print, param type: int && __ptr64
*/

推导过程

首先说明一下推导规则

规则一:

template<typename T>
f(T& param)  // 形参类型是一个引用,但不是万能引用

f(expr)  // 调用,实参是expr
  • 如果expr类型是一个引用,忽略引用部分
  • expr有const,ParamType不是const,T推导保留const性

规则二:

template<typename T>
f(T&& param)  // 形参类型是万能引用
  • 如果expr是左值,T和param都被推导为左值引用
  • 如果expr是右值,T就是右值类型,param是右值引用

规则三:

template<typename T>
f(T param)  // 形参类型是传值的方式,param就是一个拷贝
  • 如果expr类型是一个引用,忽略引用部分
  • 忽略引用性后,expr的const,volatile也忽略

std::forward 源码

// 转发左值
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }

// 转发右值
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
  static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
        " substituting _Tp is an lvalue reference type");
  return static_cast<_Tp&&>(__t);
}

实参转移过程

main(): a → foo(a) → std::forward(a) → Print(a)

其中在 foo(T&& param) 和 Print(T&& param) 这里会发生类型推导,在foo中推导的T会影响std::forward(a)中的T的类型,std::forward(a)转发得到的类型影响Print(T&& t)中T的推导

决定链为:

  1. a type → foo(T&& param)
  2. foo(T&& param) T type → std::forward(a) T type // 这里foo推导出来的T type和forward转发结果是一致的
  3. std::forward(a) return type → Print(T&& param) T type

这样一串调用下来,就能够将在main函数中a的类型,完美转发到了Print()函数中

f(a)推导

  1. main(): a 传入 foo 之前,a 的类型是 int
  2. ⇒ foo(T&& param): a 传入 foo 后,a 是左值,经过类型推导,规则二,左值推导为左值引用,所以T被推导为int&,int& && param 发生引用折叠后,param为int&
  3. ⇒ std::forward(param): 传入的param是左值,调用左值转发,此时T的类型为int&故转发的类型就是int&
  4. ⇒ Print(T&& t): 从上一个函数里传入的实参类型为int&,传入的是左值,故为T推导为左值引用int&,param引用折叠推导为int&
mainfoo(T&& param)forward(T& param)Print(T&& param)
param: intT: int&T: int&T: int&
param: int& && = int&param: int&param: int& && = int&
return: int& && = int&

f(lrefa)推导

  1. main(): lrefa传入foo之前,lrefa 的类型是 int&
  2. ⇒ foo(T&& param): 实参a的类型是int&,a是左值,故T推导为左值引用 int&,param经过引用折叠int& && 推导为 int&
  3. ⇒ std::forward(param): 传入的param是左值,调用左值转发,此时T的类型为int& 转发后仍为int&
  4. ⇒ Print(T&& t): 从上一个函数里传入的实参类型为int&,传入的是左值,故T推导为左值引用int&,param引用折叠推导为int&
mainfoo(T&& param)forward(T& param)Print(T&& param)
param: int&T: int&T: int&T: int&
param: int& && = int&param: int& & = int&param: int& && = int&
return: int& && = int&

f(rrefa)推导

  1. main(): rrefa传入foo之前,rrefa 的类型是 int&&
  2. ⇒ foo(T&& param): 实参类型是 int&&,根据推导规则二,T被推导为右值类型,即 int,param被推导为 int&& && ,引用折叠后param类型为int&&
  3. ⇒ std::forward(param): 此时T的类型为int,故std::forward(param),给forward传入的实参类别是一个左值,调用了forward的转发左值函数,传给forward的模板类型是int(这里param是一个拥有右值引用类型的左值变量),故forward返回的结果是int&&
  4. ⇒ Print(T&& t): 经过forward转发,传入的类型是int&&,故T的类型推导为int,t的类型推导为int&&
mainfoo(T&& param)forward(T& param)Print(T&& param)
param: int&&T: intT: intT: int
param: int&&param: int&param: int&&
return: int&&

理解 std::forward

// 转发左值
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }

// 转发右值
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
  static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
        " substituting _Tp is an lvalue reference type");
  return static_cast<_Tp&&>(__t);
}

从两个函数定义来看,remove_reference<_Tp>::type& __t 的参数就是一个左值引用,remove_reference<_Tp>::type&& __t 的参数就是一个右值引用,他们都不需要类型推导(因为无论_Tp推导为什么类型,都被移除引用性,只保留了基本的类型,如int,string…)。由于forward用法是forward这样就说明了具体使用哪一种转发

如果传入的是一个左值,就调用转发左值函数,如果传入的是右值,就调用转发右值函数。这里的左值和右值完全不看变量自身的类型,而是看其类别。左值和右值在生命周期方面的不同

传入参数类型调用转发函数forward<decltype(param)>(param)
param: int右值调用右值转发T: int
param: int&&
return: int&&
param: int&&左值调用左值转发T: int&&
param: int&
return: int&& && → int&&
param: int左值调用左值转发T: int
param: int&
return: int&&
param: int&左值调用左值转发T: int&
param: int&
return: int& && → int&

※任何函数的内部,对形参的直接使用,都是按左值进行的

如果要将一个右值引用类型的左值,以右值的属性传入给函数,那么就必须使用forward,将其类型的右值属性一起转发过去

不允许的情况:forward<int&>(100) 这种用法是不允许的,这种写法的意思是,将一个右值,转发成为一个左值,而右值是不能转成左值的,故触发了static_assert,禁止这种写法

转发的返回结果类型是由强制转换+引用折叠共同决定的,如forward<int>这种就是转发右值,返回结果是int&&,forward<int&>这种是转发左值引用,返回结果是int& &&→int&

模板函数的参数是万能引用,如果在函数体内传递参数,直接使用形参就会按照以左值的形式传参,使用forward就可以实现,按照原来的类型进行传参


总结

实际上,”T”的声明,正好显式表现了通过转发,将得到结果的类型,forward 转发的就是 forward<T>里面T的类型,如果T为左值引用,则转发的结果为左值引用,如果T为右值/右值引用,则转发的结果为右值引用

在使用时,如果在参数是万能引用的模板函数里,可以使用forward,如果不是万能引用的话,使用forward<decltype(param)>来转发其自身类型

forward就是为了在任何函数体内进行传参时,保持变量其自身的类型,而不是直接使用形参以左值的方式处理

💡 例:
右值转发:forward(param) —> int&& 或 forward<int&&>(param) —> int&&

左值转发:forward<int&>(param) —> int&

如果要转发其自身的类型,使用:

forward<decltype(param)>(param)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值