C++通过引入一条所谓“引用折叠”的语言规则,并结合模板推导来完成完美转发的。
首先看一个例子:
template<typename T>
void testFunc(T& r)
{
}
当我们给函数传入引用时,如果我们对函数 testFunc 传入一个非引用类型的数据,那么形参中r的类型时什么呢?传入一个右值引用,形参r中的类型是什么呢?如果函数参数声明的类型是右值引用,传入左值引用结果又会怎样呢?
模板对类型的推到规则和简单,当转发函数的实参是类型T的左值引用,那么模板参数被推导为T&类型;如果转发函数的实参是类型T的右值引用,那么模板参数被推导为T&&类型。
具体引用折叠可以参照下表:
函数参数声明的类型 | 传入实参的类型 | r的类型 |
---|---|---|
T& | TR | r& |
T& | TR& | r& |
T& | TR&& | r& |
T&& | TR | r&& |
T&& | TR& | r& |
T&& | TR&& | r&& |
从上表可以看出无论传入什么类型,右值引用都可以保持跟出入的实参类型保持不变。
所以,我们可以这么写完美转发的模板:
template<typename T>
void perfectForward(T&& t)
{
func(std::forward<T>(t));
}
下面是一个不带std::forward的示例:
#include <iostream>
#include <stdlib.h>
#include <type_traits>
void func(int& t)
{
std::cout << "func(int& t)" << std::endl;
}
void func(const int& t)
{
std::cout << "func(const int& t)" << std::endl;
}
void func(const int&& t)
{
std::cout << "func(const int&& t)" << std::endl;
}
template<typename T>
void perfectForward(T&& t)
{
//func(std::forward<T>(t));
func(t);
}
int main(int argc, char** argv)
{
int a = 0;
perfectForward(a);
const int b = 20;
perfectForward(b);
perfectForward(std::move(a));
system("pause");
return 0;
}
函数的运行结果为:
func(int& t)
func(const int& t)
func(int& t)
但是我们想让调用函数 perfectForward(10); 的时候调用函数 void func(const int&& t) 因为std::move(a)是一个右值引用。而由于引用折叠的作用,当传入一个右值到形参 T& 中会被认为推导为左值。因此,需要是使用std::forward,std::forward的作用就是强制转化为实际类型的值作为形参。
更改为std::forward 后,运行效果为:
func(int& t)
func(const int& t)
func(const int&& t)