C++ 引用折叠(Reference Collapsing)
一句话定义
当'引用的引用'在模板、typedef / using、auto、decltype中出现时,
编译器按照固定规则把它折叠为单一引用类型。
C++ 引用折叠是编译器在模板推导等场景下处理“引用的引用”的规则。核心原则是只要出现左值引用 &,结果即为 &;仅 && && 折叠为 &&。该机制发生在模板类型推导、auto、decltype 及 using/typedef 中,是实现万能引用和完美转发的基础。在 Eigen 及 SLAM 开发中,利用引用折叠配合 std::forward 可实现零拷贝传递表达式对象,显著提升性能。理解此规则有助于避免常见陷阱并编写高效模板代码。
一句话定义
当'引用的引用'在模板、typedef / using、auto、decltype中出现时,
编译器按照固定规则把它折叠为单一引用类型。
C++ 语法层面 不允许显式写:
int&&& x; // 非法
但在模板推导后,这种'引用的引用'会隐式产生:
template<typename T> void f(T&& x);
当这样调用:
int a;
f(a); // T = int&
于是参数类型变成:
T&& → int& && ← 出现'引用的引用'
引用折叠规则的存在目的:
让模板推导在语法上始终合法
只要出现左值引用
&,最终结果就是&
只有&& &&才会折叠成&&
| 原始形式 | 折叠结果 |
|---|---|
T& & | T& |
T& && | T& |
T&& & | T& |
T&& && | T&& |
'& 是霸道的,只要出现就赢'
不是任何地方都会发生引用折叠
using / typedefautodecltypestd::forward / 完美转发int&&&& x; // 语法错误(非模板上下文)
template<typename T> void f(T&& x);
int a = 10;
f(a);
推导过程:
T = int&
T&& = int& && → 折叠 → int&
x 是 左值引用
f(10);
T = int
T&& = int&&
x 是 右值引用
T&&在模板中 ≠ 右值引用
它是 万能引用(forwarding reference)
auto 中的引用折叠auto&&int a = 10;
auto&& x = a;
推导:
auto = int&
auto&& = int& && → int&
auto&& y = 10;
auto = int
auto&& = int&&
auto&& // 永远是万能引用
using / typedef 中的引用折叠using LRef = int&;
using RRef = int&&;
LRef& → int&
LRef&& → int&
RRef& → int&
RRef&& → int&&
using 不会阻止引用折叠
decltype + 引用折叠(最容易踩坑)int x = 10;
decltype(x) // int
decltype((x)) // int& ← 注意括号!
decltype((x))&& y = x;
推导:
decltype((x)) = int&
int& && → int&
decltype的结果本身可能带引用
再加&&就会触发引用折叠
std::forward 的本质template<typename T> T&& forward(remove_reference_t<T>& param);
template<typename T> void wrapper(T&& arg) {
foo(std::forward<T>(arg));
}
T = int&
forward<int&>(arg) → int&
T = int
forward<int>(arg) → int&&
引用折叠是完美转发成立的核心机制
T&& 一定是右值引用template<typename T> void f(T&& x); // 错
正解:
当且仅当 T 是被推导出来的,T&& 才是万能引用
&& 期望折叠void f(int&&&& x); // 错误
decltype((x)) 是引用引用折叠不是运行期行为
它发生在模板实例化 + 类型替换阶段
模板推导 → 生成候选类型 → 引用折叠 → 最终参数类型 → 代码生成
1️⃣ 引用折叠只在模板/类型推导中发生
2️⃣ 规则只有一条:有 & 就是 &
3️⃣T&&+ 模板推导 = 万能引用
4️⃣ 完美转发的底层机制 = 引用折叠
5️⃣decltype((x))是最常见陷阱
在 SLAM 代码中经常会写:
Eigen::Matrix<double, 15, 15> H;
Eigen::Matrix<double, 15, 1> b;
或者:
Eigen::Vector3d r;
Eigen::Matrix3d J;
void AddResidual(Eigen::VectorXd r) { // 每次调用都会拷贝
}
Eigen 动态矩阵 → 昂贵拷贝
Matrix = 表达式
表达式不是值,而是 Expression Template
template<typename Derived> void AddResidual(Eigen::MatrixBase<Derived> r) { // 拷贝已经发生
}
template<typename Derived> void AddResidual(const Eigen::MatrixBase<Derived>& r);
template<typename Derived> void AddResidual(Eigen::MatrixBase<Derived>&& r) { // 完美接收左值 or 右值
}
这是 Eigen 内部大量使用的模式
Eigen::Vector3d r;
AddResidual(r);
T = Eigen::Vector3d&
T&& = Eigen::Vector3d& && → 折叠 → Eigen::Vector3d&
r 以 左值引用 进入
无拷贝
AddResidual(J * dx + r0);
这里:
J * dx + r0 → Eigen::CwiseBinaryOp<...>(表达式)
T = CwiseBinaryOp<...>
T&& = CwiseBinaryOp<...>&&
表达式对象 零拷贝传入
计算 延迟到函数内部
const& 在 Eigen 中不够好?const Eigen::MatrixBase<Derived>& r
template<typename Derived> void foo(Eigen::MatrixBase<Derived>&& x);
并在内部:
auto&& expr = std::forward<Derived>(x);
template<typename ResidualDerived, typename JacobianDerived>
void AddFactor(ResidualDerived&& r, JacobianDerived&& J) {
using RType = std::remove_reference_t<ResidualDerived>;
using JType = std::remove_reference_t<JacobianDerived>;
// 仅在必要时 eval
const RType& r_eval = r;
const JType& J_eval = J;
// 参与正规方程
}
AddFactor(
J * dx + r0, // 右值表达式
J.transpose() * J // 右值表达式
);
template<typename TangentDerived> void ApplyUpdate(TangentDerived&& delta) {
// delta 是 Eigen::Matrix<double, 6, 1> 或表达式
xi_ = Sophus::SE3d::exp(delta) * xi_;
}
Eigen::Matrix<double, 6, 1> dx;
ApplyUpdate(dx);
→ delta 折叠为 Eigen::Matrix<...>&
ApplyUpdate(H.ldlt().solve(b));
→ delta 是 Eigen 表达式
→ 无临时矩阵拷贝
template<typename Derived> void Foo(Derived&& x) {
Bar(std::forward<Derived>(x));
}
Bar(x); // 丢失值类别 → 右值变左值
| 写法 | 是否拷贝 | 表达式延迟 | 工程推荐 |
|---|---|---|---|
| 值传递 | ❌ | ❌ | 禁用 |
const& | ✔ | 部分 | 一般 |
T&& + forward | ✔✔ | ✔✔ | ⭐⭐⭐ |
任何可能接收 Eigen 表达式 / 李代数增量的接口:
template<typename T> void func(T&& x);
并在内部:
auto&& v = std::forward<T>(v);
template<typename R, typename J> void AddResidual(R&& r, J&& J) { ... }
template<typename DX> void Update(DX&& dx);
template<typename Measurement> Factor(Measurement&& z);
Eigen 的高性能 = 表达式模板
表达式模板的生命线 = 引用折叠 + 完美转发

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online