C++ 引用折叠(Reference Collapsing)的具体使用

 更新时间:2026年05月19日 09:15:07   作者:点云SLAM  
本文主要介绍了C++ 引用折叠使用,,引用折叠主要在模板类型推导、auto、decltype等场景中发生,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一句话定义
当“引用的引用”在模板、typedef / using、auto、decltype 中出现时,
编译器按照固定规则把它折叠为单一引用类型。

一、为什么需要引用折叠?

C++ 语法层面 不允许显式写

int&&& x;   //  非法

但在模板推导后,这种“引用的引用”会隐式产生

template<typename T>
void f(T&& x);

当这样调用:

int a;
f(a);       // T = int&

于是参数类型变成:

T&& → int& &&   ← 出现“引用的引用”

引用折叠规则的存在目的:

让模板推导在语法上始终合法

二、唯一的折叠规则

C++ 标准中的核心规则

只要出现左值引用 &,最终结果就是 &
只有 && && 才会折叠成 &&

四种情况

原始形式折叠结果
T& &T&
T& &&T&
T&& &T&
T&& &&T&&

口诀版

“& 是霸道的,只要出现就赢”

三、引用折叠只会在这些地方发生

不是任何地方都会发生引用折叠

会发生的场景

  1. 模板类型推导
  2. using / typedef
  3. auto
  4. decltype
  5. std::forward / 完美转发

不会发生的场景

int&& && x;     // 语法错误(非模板上下文)

四、模板推导中的引用折叠(最重要)

1 万能引用(Forwarding Reference)

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中的引用折叠

示例 1: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)) 是最常见陷阱

Eigen/SLAM 中:引用折叠如何避免拷贝

一、Eigen 场景中的真实问题

在 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 动态矩阵 → 昂贵拷贝

二、Eigen 官方的解决方案:模板 + 表达式

Eigen 的核心思想

Matrix = 表达式

表达式不是值,而是 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) {
    // 完美接收左值 or 右值
}

这是 Eigen 内部大量使用的模式

五、引用折叠在这里如何工作?

情况 1:传左值(已有残差向量)

Eigen::Vector3d r;
AddResidual(r);

推导过程

T = Eigen::Vector3d&
T&& = Eigen::Vector3d& && → 折叠 → Eigen::Vector3d&

r左值引用 进入
无拷贝

情况 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>;

    // 仅在必要时 eval
    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) {
    // 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));

deltaEigen 表达式
无临时矩阵拷贝

九、std::forward 在 Eigen / SLAM 中的作用

正确姿势

template<typename Derived>
void Foo(Derived&& x) {
    Bar(std::forward<Derived>(x));
}

错误姿势

Bar(x);  // 丢失值类别 → 右值变左值

十、性能视角总结(非常重要)

写法是否拷贝表达式延迟工程推荐
值传递禁用
const&部分一般
T&& + forward✔✔✔✔⭐⭐⭐

十一、Eigen / SLAM 模板黄金法则

任何可能接收 Eigen 表达式 / 李代数增量的接口:

template<typename T>
void func(T&& x);

并在内部:

auto&& v = std::forward<T>(x);

十二、在 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 的高性能 = 表达式模板
表达式模板的生命线 = 引用折叠 + 完美转发

到此这篇关于C++ 引用折叠(Reference Collapsing)的具体使用的文章就介绍到这了,更多相关C++ 引用折叠内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言中杨氏矩阵与杨辉三角的实现方法

    C语言中杨氏矩阵与杨辉三角的实现方法

    这篇文章主要给大家介绍了关于C语言中杨氏矩阵与杨辉三角的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05
  • C++如何实现BCD码和ASCII码的相互转换

    C++如何实现BCD码和ASCII码的相互转换

    这篇文章主要介绍了C++实现BCD码和ASCII码互转,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • Qt使用windeployqt工具实现程序打包发布方法

    Qt使用windeployqt工具实现程序打包发布方法

    本文主要介绍了Qt使用windeployqt工具实现程序打包发布方法,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • C++中浅拷贝与深拷贝的详解及其作用介绍

    C++中浅拷贝与深拷贝的详解及其作用介绍

    这篇文章主要介绍了C++中浅拷贝与深拷贝的详解及其作用介绍,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • c语言如何输入带\n和空格的字符串

    c语言如何输入带\n和空格的字符串

    这篇文章主要介绍了c语言如何输入带\n和空格的字符串问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-07-07
  • C++ 网络连通性检测的实现方法

    C++ 网络连通性检测的实现方法

    这篇文章主要介绍了C++ 网络连通性检测的实现方法的相关资料,这里提供实例帮助大家实现这样的功能,需要的朋友可以参考下
    2017-09-09
  • C++实现屏幕截图功能

    C++实现屏幕截图功能

    这篇文章主要为大家详细介绍了C++实现屏幕截图功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-05-05
  • Qt编写地图实现实时动态轨迹效果

    Qt编写地图实现实时动态轨迹效果

    实时动态轨迹主要是需要在地图上动态显示GPS的运动轨迹,也是编写地图时一个重要的功能。本文将利用Qt实现这一功能,需要的可以参考一下
    2022-02-02
  • C语言文件操作函数大全(超详细)

    C语言文件操作函数大全(超详细)

    本篇文章是对C语言中的文件操作函数进行了详细的总结分析,需要的朋友参考下
    2013-05-05
  • C++关于构造函数可向父类或者本类传参的讲解

    C++关于构造函数可向父类或者本类传参的讲解

    今天小编就为大家分享一篇关于C++关于构造函数可向父类或者本类传参的讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12

最新评论