目录
一,引言
在C++11引入右值引用之后,就存在引用的引用。如下:
cpp
template<class T>
void f1(T& x)
{}
T的类型就有很多种,如T可以实例化为原本类型,也可以T&,或者T&&。为此引入引用折叠的概念。
二,引用折叠
在进行模板或typedef时会构成引用的引用。首先需要先介绍引用折叠的规则:
cpp
typedef int& lref;
typedef int&& rref;
int n = 0;
lref& r1 = n;
lref&& r2 = n;
rref& r3 = n;
rref&& r4 = 1;
首先lref的类型是int&。rref的类型是int&&。n的类型是int。
r1的类型是int& ----------------int& 和 &进行引用折叠折叠成int&
r2的类型是int& -----------------int& 和 &&进行引用折叠折叠成int&
r3的类型是int& -----------------int&& 和 & 进行引用折叠折叠成int&
r3的类型是int&& ----------------int&& 和 && 进行引用折叠折叠成int&&
由此可以发现只有右值引用和右值引用进行引用折叠才可以折叠成右值引用,其余情况经过引用折叠全都折叠成左值引用。下面来举两个例子加深以下理解:
案例一:
cpp
template<class T>
void f1(T& x)
{}
第一种情况:
cpp
int n = 0;
f1<int>(n);
f1<int>(0);
T通过显示实例化为int。f1为左值引用,n为左值。0为右值。所以第一个可以运行,第二个报错,左值不能传右值。
第二种情况:
cpp
f1<int&>(n);
f1<int&>(0);
T通过显示实例化为int&。经过引用折叠折叠为void f1(int& x) 。n为左值,0为右值。第二个报错。
第三种情况:
cpp
f1<int&&>(n);
f1<int&&>(0);
T通过显示实例化为int&&。经过引用折叠为void f1(int& x) 。n为左值,0为右值。第二个报错。
第四种情况:
cpp
f1<const int&>(n);
f1<const int&>(0);
T通过显示实例化为const int& 。经过引用折叠为void f1(const int& x) 。const左值可以接收右值。上述两种情况都可以通过。
第五种情况:
cpp
f1<const int&&>(n);
f1<const int&&>(0);
T通过显示实例化为const int&。经过引用折叠为void f1(const int& x) 。const左值可以接收右值。
下面来看当函数的参数是右值的情况:
cpp
template<class T>
void f2(T&& x)
{}
第一种情况:
cpp
f2<int&>(n);
f2<int&>(0);
T实例化为int&。经过引用折叠为void f2(int& x)。因此运行结果和上述第二种是一致的。
第二种情况:
cpp
f2<int&&>(n);
f2<int&&>(0);
T实例化为int&&。经过引用折叠为void f2(int&& x) 。因此当传入左值会报错。只能传入右值。
上述都是传参时经过显示实例化。下面来举不显示实例化,由编译器自行进行推到的情况。
案例二:
cpp
template<class T>
void Function(T&& t)
{
int a = 0;
T x = a;
//x++;
cout << &a << endl;
cout << &x << endl << endl;
}
第一种情况:
cpp
Function(10);
10为右值函数识别T的类型为int。函数实例化为void Function(int&& t)。
第二种情况:
cpp
int a;
Function(a);
a为左值函数识别T的类型为int&。经过引用折叠,函数实例化为void Function(int& t)。
总结 :右值引用和左值引用进行引用折叠时,总会折叠为左值引用。在上述案例二中,参数为右值引用,由于引用折叠的原因不管参数传入左值还是右值都可以通过引用折叠的功能进行解决。当传入左值时,就会折叠为该类型的引用。当传入右值时,不进行折叠。因此案例二的参数引用模式称为万能引用。
三,完美转发
在上一章中讲过表达式本身是左值属性。也就意味着不管是左值引用还是右值引用。参数变量的属性都是左值属性。为此C++为了保留该变量的原本属性引入了完美转发的概念。例如:
上一节中当传入右值时,函数实例化为void Function(int&& t)。虽然是右值引用,但是t的属性为左值。为了使得t的属性依旧保持为右值属性。需要下面关键字:

举个例子:
cpp
template <class T>
void fun(T&& n)
{
}
template<class T>
void f1(T&& x)
{
fun(std::forward<T>(x));
}
int main()
{
f1(0);
return 0;
}
如果不进行forward修饰x的属性为左值。通过fun函数引用折叠为void fun(int& n);若经过forward修饰之后。x依旧为右值属性。则fun函数不需要引用折叠实例化为fun (int&& n)。