引用折叠(Reference Collapsing)是 C++11 引入的核心规则,专门解决模板 / 自动类型推导中 "多层引用叠加" 的问题(比如同时出现左值引用和右值引用),是实现「万能引用(Universal Reference)」和「完美转发」的底层基础。
一、引用折叠的规则
C++ 中不允许直接定义 "引用的引用" (如 int& & 是非法的),但模板推导、auto 推导等场景会间接产生 "引用叠加",此时编译器会按以下规则自动 "折叠" 为单一引用:
| 叠加的引用类型 | 折叠结果 | 变化 |
|---|---|---|
| 左值引用 + 左值引用 | 左值引用 | T& & → T& |
| 左值引用 + 右值引用 | 左值引用 | T& && → T& |
| 右值引用 + 左值引用 | 左值引用 | T&& & → T& |
| 右值引用 + 右值引用 | 右值引用 | T&& && → T&& |
只要叠加的引用中有「左值引用(&)」,最终结果就是左值引用
二、为什么需要引用折叠?
没有引用折叠,模板推导中会出现 "引用的引用" 这种非法语法 ,导致万能引用(T&&)和完美转发无法实现。
无引用折叠会报错:
cpp
template <typename T>
void func(T&& arg) {} // 万能引用,T的类型由实参推导
int main() {
int a = 10;
int& ref_a = a;
func(ref_a); // 实参是左值引用,推导T为int&--形参变为int& &&(非法,需折叠)
return 0;
}
此时模板推导后,T 被推导为 int&,形参 arg 的类型本应是 int& &&(引用的引用),但引用折叠规则会将其折叠为 int&(左值引用),从而避免语法错误。
三、引用折叠的应用
引用折叠的核心价值是支撑**「万能引用」** (也叫转发引用),而万能引用是完美转发的基础。
1. 万能引用的类型推导
万能引用的定义:模板中形如 T&& 的参数,且 T 是模板参数 /auto 推导的类型 。它的本质是:通过引用折叠,让 T&& 既能接收左值引用,也能接收右值引用。
万能引用的推导过程
cpp
#include <iostream>
#include <type_traits>
using namespace std;
// 万能引用模板
template <typename T>
void checkType(T&& arg) {
// 打印推导后的类型
if (is_lvalue_reference<decltype(arg)>::value) {
cout << "最终类型:左值引用" << endl;
} else if (is_rvalue_reference<decltype(arg)>::value) {
cout << "最终类型:右值引用" << endl;
}
}
int main() {
int a = 10;
int& lref = a; // 左值引用
int&& rref = 20; // 右值引用
// 实参是左值(a)
checkType(a); // T推导为int& → arg类型:int& && → 折叠为int&(左值引用)
// 实参是左值引用(lref)
checkType(lref); // T推导为int& → arg类型:int& && → 折叠为int&(左值引用)
// 实参是右值(20)
checkType(20); // T推导为int → arg类型:int&& && → 折叠为int&&(右值引用)
// 实参是右值引用(rref)
checkType(rref); // T推导为int& → arg类型:int& && → 折叠为int&(左值引用)
// 实参是std::move(rref)(强制转为右值)
checkType(std::move(rref)); // T推导为int → arg类型:int&& && → 折叠为int&&(右值引用)
return 0;
}
注意:右值引用变量本身是左值!
输出结果:
最终类型:左值引用
最终类型:左值引用
最终类型:右值引用
最终类型:左值引用
最终类型:右值引用
关键推导逻辑(结合引用折叠)
| 实参类型 | T 的推导结果 | 形参原始类型 | 折叠后类型 |
|---|---|---|---|
| 左值(int a) | int& | int& && | int& |
| 右值(20) | int | int&& && | int&& |
2. 完美转发
完美转发的目标是:将参数原封不动地传递给内部函数,保留其左 / 右值属性 。实现依赖:std::forward<T>(arg) 结合引用折叠,还原参数的原始类型。
完美转发的底层逻辑
cpp
// 被转发的函数(区分左/右值)
void process(int& x) { cout << "处理左值:" << x << endl; }
void process(int&& x) { cout << "处理右值:" << x << endl; }
// 转发函数(万能引用 + 引用折叠 + std::forward)
template <typename T>
void forwardFunc(T&& arg) {
// std::forward<T> 会根据T的类型,还原arg的原始属性:
// - 若T是int& → forward返回int&(左值引用)
// - 若T是int → forward返回int&&(右值引用)
process(std::forward<T>(arg));
}
int main() {
int a = 10;
forwardFunc(a); // 转发左值 → 调用process(int&)
forwardFunc(20); // 转发右值 → 调用process(int&&)
return 0;
}
输出结果:
处理左值:10
处理右值:20
std::forward 的简化实现
cpp
template <typename T>
T&& forward(typename remove_reference<T>::type& arg) noexcept {
return static_cast<T&&>(arg); // 核心:通过T的类型触发引用折叠
}
- 当
T是int&时,static_cast<int& &&>(arg)→ 折叠为int&(左值引用); - 当
T是int时,static_cast<int&&>(arg)→ 右值引用。
四、需要注意的
-
"引用的引用" 仅在推导中出现,不能直接定义:
int a = 10; int& &ref = a; // 直接写:编译错误(不允许显式定义引用的引用)只有模板 /auto 推导这种 "间接场景",编译器才会通过引用折叠处理。
-
右值引用变量本身是左值 :
int&& rref = 20;中,rref是 "右值引用类型的变量",但变量本身是左值(有名字、可取地址),因此传递给万能引用时,T会推导为int&,最终折叠为左值引用。 -
引用折叠仅针对 "顶层引用" :若类型是 "嵌套引用"(如
int& (*)[5],指针指向左值引用数组),折叠规则不生效 ------ 仅对直接的 "引用叠加" 生效。