万能引用
在C++中,模板函数中的 T&&
类型参数,当与类型推导结合使用时,被称为“万能引用”(universal reference)。这意味着它可以绑定到左值也可以绑定到右值。这是因为当模板类型推导发生时,编译器会根据传递给函数的实参是左值还是右值来决定 T
的类型。
template<class T>
void func(T &&val);
- 如果你传递一个左值
L
,T
会被推导为L&
,因此T&&
实际上就变成了L& &&
,根据引用折叠规则,它会折叠成L&
。 - 如果你传递一个右值
R
,T
会被推导为R
,因此T&&
就是R&&
。
这就是为什么模板函数可以接受左值和右值引用:因为类型推导和引用折叠使得 T&&
可以根据传入的实参类型变成左值引用或右值引用。
对于非模板函数:
void func(int &&val);
这里的 int&&
是一个明确的右值引用,它只能绑定到右值。它不涉及类型推导,因此没有万能引用的行为。这个函数只接受右值,因为 int&&
明确地要求实参必须是一个右值。
模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,我们希望能够在传递过程中保持它的左值或者右值的属性。如果不使用forward,直接按照下面的方式写就会导致问题。
void RFn(int&& arg){
}
template<typename T>
void ProxyFn(T&& arg){
RFn(arg);
}
void main(){
ProxyFn(1);
}
会发现右值版本不能传过去, [int]无法到[int&&],就导致参数不匹配。
为了解决这个问题,引入了std::forward, 将模板函数改成如下形式就可以了, forward被称为完美转发
“`c++
template<typename T>
void ProxyFn(T&& arg){
RFn(std::forward<T>(arg));
}
<pre><code class="line-numbers">## forward的实现原理与细节
“`cpp
Class Base{
public:
Base(const Base& b){
// copy construct
}
Base(Base&& b){
//move construct
}
};
template<typename T>
void ProxyFn(T&& arg){
Base(std::forward<T>(arg));
}
void main(){
Base b;
ProxyFn(b);
}
整个推导转发的过程如下图
图中所说的T会被推导成Base&
,是因为在万能引用中,编译器有一个规则,如果传入的是左值,则模板类型会被推导成左值引用类型; 传入的是右值,则模板类型就是值的类型
。
引用折叠的规则如下:
Base&& && -> Base&&
Base& && -> Base&
Base&& & -> Base&
Base& & -> Base&
std::forward<T>(arg)
,其是实现完美转发的关键,这个forward中的T
把Base&
给传递了过去, 然后在forward中_Ty&&
进行折叠推导后,就变成了Base&,这就使得static_cast
以及返回的类型都是Base&
。
这里有两个很重要的点,返回的类型虽然是Base&
,但前文不是说引用只是用来对接收参数的类型起限制作用,后续使用的时候就完全退化成了左值了吗? forward的完美转发对于返回的是Base&
类型还好,但加入推导返回的是Base&&
,传递到Base去构造的时候,不还是传一个左值吗,匹配到还是copy construct,无法达到完美转发需求吗?这一块就牵涉到c++标准中的对左值右值的规定,返回值中, 左值引用的值类型是左值,右值引用的值类型是右值,当 rvalue 作为函数参数使用时,如果函数有两个重载,一个重载使用 rvalue 引用参数,另一个重载使用 lvalue 引用常量参数,那么 rvalue 将绑定到 rvalue 引用重载(因此,如果复制和移动构造函数都可用,那么 rvalue 参数将调用移动构造函数,复制和移动赋值操作符也是如此)。
两个版本
其次,forward其实有两个版本,但是上面的例子中只给出了一个,因为我们是在万能引用的场景中使用std::forwared<>,因为传递的是左值,所以优先匹配是forward左值引用的版本, 正如其注释所言,将一个左值转发成一个左值或者右值
,上面分析过这是由模板类型_Ty所决定的最终转发成什么类型;当传递给forward的是一个右值的时候,才会去匹配第二个万能引用版本。
// FUNCTION TEMPLATE forward
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(
remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
return static_cast<_Ty&&>(_Arg);
}
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
return static_cast<_Ty&&>(_Arg);
}
forward配合万能引用转发右值