C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++ 引用折叠

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

作者:点云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);

四、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

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++ 引用折叠内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文