跳到主要内容C++ 引用折叠规则与模板推导应用详解 | 极客日志C++AI算法
C++ 引用折叠规则与模板推导应用详解
C++ 引用折叠是编译器在模板推导等场景下处理“引用的引用”的规则。核心原则是只要出现左值引用 &,结果即为 &;仅 && && 折叠为 &&。该机制发生在模板类型推导、auto、decltype 及 using/typedef 中,是实现万能引用和完美转发的基础。在 Eigen 及 SLAM 开发中,利用引用折叠配合 std::forward 可实现零拷贝传递表达式对象,显著提升性能。理解此规则有助于避免常见陷阱并编写高效模板代码。
黑客帝国22 浏览 C++ 引用折叠(Reference Collapsing)
一句话定义
当'引用的引用'在模板、typedef / using、auto、decltype 中出现时,
编译器按照固定规则把它折叠为单一引用类型。
一、为什么需要引用折叠?
C++ 语法层面 不允许显式写:
int&&& x;
但在模板推导后,这种'引用的引用'会隐式产生:
template<typename T> void f(T&& x);
当这样调用:
int a;
f(a);
于是参数类型变成:
T&& → int& && ← 出现'引用的引用'
引用折叠规则的存在目的:
让模板推导在语法上始终合法
二、唯一的折叠规则
C++ 标准中的核心规则
只要出现左值引用 &,最终结果就是 &
只有 && && 才会折叠成 &&
四种情况
| 原始形式 | 折叠结果 |
|---|
T& & | T& |
T& && | T& |
T&& & | T& |
T&& && | T&& |
口诀版
'& 是霸道的,只要出现就赢'
三、引用折叠只会在这些地方发生
会发生的场景
- 模板类型推导
using / typedef
auto
decltype
std::forward / 完美转发
不会发生的场景
四、模板推导中的引用折叠(最重要)
1 万能引用(Forwarding Reference)
template<typename T> void f(T&& x);
调用情况分析
T = int&
T&& = int& && → 折叠 → int&
结论(非常重要)
T&& 在模板中 ≠ 右值引用
它是 万能引用(forwarding reference)
五、auto 中的引用折叠
示例 1:auto&&
int a = 10;
auto&& x = a;
auto = int&
auto&& = int& && → int&
auto = int
auto&& = int&&
结论
六、using / typedef 中的引用折叠
示例
using LRef = int&;
using RRef = int&&;
LRef& → int&
LRef&& → int&
RRef& → int&
RRef&& → int&&
七、decltype + 引用折叠(最容易踩坑)
规则回顾
int x = 10;
decltype(x)
decltype((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&& 才是万能引用
在非模板中使用 && 期望折叠
忘了 decltype((x)) 是引用
十、编译器视角总结
引用折叠不是运行期行为
它发生在模板实例化 + 类型替换阶段
编译流程中位置
模板推导 → 生成候选类型 → 引用折叠 → 最终参数类型 → 代码生成
十一、终极总结
1️⃣ 引用折叠只在模板/类型推导中发生
2️⃣ 规则只有一条:有 & 就是 &
3️⃣ T&& + 模板推导 = 万能引用
4️⃣ 完美转发的底层机制 = 引用折叠
5️⃣ decltype((x)) 是最常见陷阱
Eigen/SLAM 中:引用折叠如何避免拷贝
一、Eigen 场景中的真实问题
Eigen::Matrix<double, 15, 15> H;
Eigen::Matrix<double, 15, 1> b;
Eigen::Vector3d r;
Eigen::Matrix3d J;
天真的接口(会拷贝)
void AddResidual(Eigen::VectorXd r) {
}
二、Eigen 官方的解决方案:模板 + 表达式
Eigen 的核心思想
表达式不是值,而是 Expression Template
三、错误写法 vs 正确写法(关键对比)
错误:值传递
template<typename Derived> void AddResidual(Eigen::MatrixBase<Derived> r) {
}
错误:const 引用但阻断右值
template<typename Derived> void AddResidual(const Eigen::MatrixBase<Derived>& r);
- 不能高效接收临时表达式
- 失去 move / lazy evaluation 的机会
四、Eigen 推荐的'零拷贝'接口写法
核心模式(引用折叠的舞台)
template<typename Derived> void AddResidual(Eigen::MatrixBase<Derived>&& r) {
}
五、引用折叠在这里如何工作?
情况 1:传左值(已有残差向量)
Eigen::Vector3d r;
AddResidual(r);
推导过程
T = Eigen::Vector3d&
T&& = Eigen::Vector3d& && → 折叠 → Eigen::Vector3d&
情况 2:传右值表达式(Eigen 的精华)
AddResidual(J * dx + r0);
J * dx + r0 → Eigen::CwiseBinaryOp<...>(表达式)
推导过程
T = CwiseBinaryOp<...>
T&& = CwiseBinaryOp<...>&&
六、为什么 const& 在 Eigen 中不够好?
问题
const Eigen::MatrixBase<Derived>& r
- 强制绑定 const
- 阻止某些 move / eval
- 某些表达式不能安全延迟
Eigen 官方风格
template<typename Derived> void foo(Eigen::MatrixBase<Derived>&& x);
auto&& expr = std::forward<Derived>(x);
七、SLAM 后端:残差 & Jacobian 的真实例子
一个真实的误差项接口
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>;
const RType& r_eval = r;
const JType& J_eval = J;
}
调用方式(零拷贝)
AddFactor(
J * dx + r0,
J.transpose() * J
);
八、结合李代数(SO(3) / SE(3))
常见 SLAM 代码
template<typename TangentDerived> void ApplyUpdate(TangentDerived&& delta) {
xi_ = Sophus::SE3d::exp(delta) * xi_;
}
左值情况
Eigen::Matrix<double, 6, 1> dx;
ApplyUpdate(dx);
→ delta 折叠为 Eigen::Matrix<...>&
右值情况
ApplyUpdate(H.ldlt().solve(b));
→ delta 是 Eigen 表达式
→ 无临时矩阵拷贝
九、std::forward 在 Eigen / SLAM 中的作用
正确姿势
template<typename Derived> void Foo(Derived&& x) {
Bar(std::forward<Derived>(x));
}
错误姿势
十、性能视角总结(非常重要)
| 写法 | 是否拷贝 | 表达式延迟 | 工程推荐 |
|---|
| 值传递 | ❌ | ❌ | 禁用 |
const& | ✔ | 部分 | 一般 |
T&& + forward | ✔✔ | ✔✔ | ⭐⭐⭐ |
十一、Eigen / SLAM 模板黄金法则
任何可能接收 Eigen 表达式 / 李代数增量的接口:
template<typename T> void func(T&& x);
auto&& v = std::forward<T>(v);
十二、在 SLAM 系统中应该立刻用的模式
后端残差
template<typename R, typename J> void AddResidual(R&& r, J&& J) { ... }
状态更新
template<typename DX> void Update(DX&& dx);
图优化 factor 构造
template<typename Measurement> Factor(Measurement&& z);
总结
Eigen 的高性能 = 表达式模板
表达式模板的生命线 = 引用折叠 + 完美转发
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- 随机西班牙地址生成器
随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online