【Effective Modern C++】第五章 右值引用、移动语义和完美转发:28. 理解引用折叠

引用折叠是 C++11 引入的底层编译期规则 ,是通用引用(转发引用)、std::forward能正常工作的核心 ------C++ 语法明确禁止显式声明 "引用的引用" (比如int& &),但编译器在特定场景下会隐式生成引用的引用,此时靠 "引用折叠规则" 将其化简为单个引用,这也是通用引用能同时绑定左值 / 右值的根本原因。

通用引用的类型推导编码规则

对于通用引用模板:

c++ 复制代码
template<typename T>
void func(T&& param); // param是通用引用

模板参数T会根据传入实参的 "值类别"(左值 / 右值)做编码推导

  • 传入左值T推导为左值引用(比如传入Widget左值,T= Widget&);
  • 传入右值T推导为非引用类型(比如传入Widget右值,T= Widget)。

这种 "不对称编码" 是理解引用折叠的关键,比如:

c++ 复制代码
Widget w; // 左值
Widget widgetFactory(); // 返回右值的函数

func(w); // 左值传参 → T推导为Widget&
func(widgetFactory()); // 右值传参 → T推导为Widget

引用折叠的规则

1. 为什么需要引用折叠?

以上面左值传参为例:把推导后的T=Widget&代入模板,会得到:

c++ 复制代码
void func(Widget& && param); // 引用的引用(显式写会报错,但编译器隐式生成时允许)

编译器不会报错,而是通过引用折叠规则将 "引用的引用" 化简为单个引用。

2. 折叠规则(有左则左,全右才右)

C++ 有左值引用(T&)和右值引用(T&&)两种,因此 "引用的引用" 有 4 种组合,折叠后仅两种结果:

引用组合 折叠结果 规则说明
左值引用 + 左值引用 左值引用 只要有一个是左值引用,结果就是左值引用
左值引用 + 右值引用 左值引用 同上
右值引用 + 左值引用 左值引用 同上
右值引用 + 右值引用 右值引用 只有全是右值引用,结果才是右值引用

因此,Widget& && 折叠后就是 Widget&,最终函数签名为:

c++ 复制代码
void func(Widget& param); // 通用引用绑定左值,最终是左值引用

引用折叠是 std::forward 的底层实现原理

std::forward的 "完美转发" 能力,完全依赖引用折叠规则。

1. std::forward 的简化实现(C++11 版)

c++ 复制代码
template<typename T>
T&& forward(typename remove_reference<T>::type& param) {
    return static_cast<T&&>(param);
}

(C++14 可通过remove_reference_t<T>简化,去掉typename::type

2. 分场景拆解 forward 的实例化(结合引用折叠)

场景 1:传入左值(比如Widget w; func(w);
  • T推导为Widget&,代入 forward:
c++ 复制代码
    // 第一步:代入T=Widget&
    Widget& && forward(remove_reference<Widget&>::type& param) {
        return static_cast<Widget& &&>(param);
    }
    // 第二步:remove_reference<Widget&>::type = Widget
    Widget& && forward(Widget& param) {
        return static_cast<Widget& &&>(param);
    }
    // 第三步:引用折叠化简
    Widget& forward(Widget& param) {
        return static_cast<Widget&>(param);
    }
    ```
    
- 结果:forward 返回左值引用(左值),无任何转换,左值实参 "原样转发"。

#### 场景 2:传入右值(比如`func(widgetFactory());`)

- `T`推导为`Widget`,代入 forward:

```c++
    // 第一步:代入T=Widget
    Widget&& forward(remove_reference<Widget>::type& param) {
        return static_cast<Widget&&>(param);
    }
    // 第二步:remove_reference<Widget>::type = Widget
    Widget&& forward(Widget& param) {
        return static_cast<Widget&&>(param);
    }
    ```
    
- 结果:forward 返回右值引用(右值),把原本是左值的形参`param`转为右值,实现 "右值转发"。

`std::forward`的本质是:**利用引用折叠规则,根据 T 的推导类型(左值引用 / 非引用),有条件地将形参转为右值**------ 只有传入的是右值时,才转成右值;传入左值时,保持左值。

## 引用折叠发生的 4 种场景

编译器只会在 4 种特定场景下隐式生成 "引用的引用",并触发引用折叠,条款 28 逐一详解:

### 场景 1:模板实例化(最常见)

通用引用模板实例化时的类型替换(如前面`func`的例子),是引用折叠最核心的应用场景。

### 场景 2:auto 类型推导

`auto&&`的推导逻辑和模板完全一致(通用引用的另一种形式),推导后触发折叠:

```c++
Widget w; // 左值
Widget widgetFactory(); // 右值

// 场景2.1:auto&&绑定左值
auto&& w1 = w; 
// auto推导为Widget& → 代入得Widget& && → 折叠为Widget&(左值引用)

// 场景2.2:auto&&绑定右值
auto&& w2 = widgetFactory();
// auto推导为Widget → 代入得Widget&& → 无折叠(右值引用)

场景 3:typedef / 别名声明的创建与使用

创建 typedef / 别名时若出现 "引用的引用",触发折叠:

c++ 复制代码
template<typename T>
class Widget {
public:
    typedef T&& RvalueRefToT; // 声明"右值引用"的typedef
};

// 用左值引用实例化Widget
Widget<int&> w;
// 代入T=int& → typedef int& && RvalueRefToT → 折叠为int&
// 最终typedef名"RvalueRefToT"实际是左值引用(名字和实际类型不符)

场景 4:decltype 的使用

分析decltype表达式时若出现 "引用的引用",触发折叠(参考 Item3):

比如通过复杂表达式推导后得到 "引用的引用",编译器会按规则折叠为单个引用。

通用引用的本质

通用引用不是新类型,它只是 "满足两个条件的右值引用(T&&)":

  1. 类型推导能区分左值 / 右值(T 的推导编码规则:左值→T&,右值→T);
  2. 推导后的类型代入会触发引用折叠。

简单说:T&&只有在 "能推导 + 会折叠" 的上下文里,才是通用引用;否则就是普通右值引用。

总结

  1. 引用折叠仅发生在 4 种场景:模板实例化、auto 类型推导、typedef / 别名声明、decltype 使用;
  2. 折叠规则:有左值引用参与则结果为左值引用,仅全为右值引用时结果为右值引用;
  3. 通用引用是 "特定上下文的右值引用"------ 上下文需满足 "类型推导区分左 / 右值 + 触发引用折叠";
  4. 引用折叠是通用引用、std::forward 能工作的底层核心。

原著在线阅读地址

相关推荐
enjoy嚣士2 小时前
Java 之 实现C++库函数等价函数遇到的问题
java·开发语言·c++
Ivanqhz2 小时前
半格与数据流分析的五个要素(D、V、F、I、Λ)
开发语言·c++·后端·算法·rust
元让_vincent3 小时前
DailyCoding C++ | SLAM里的“幽灵数据”:从一个未初始化的四元数谈C++类设计
开发语言·c++·slam·构造函数·类设计·激光里程计
A9better3 小时前
C++——指针与内存
c语言·开发语言·c++·学习
今儿敲了吗4 小时前
18| 差分数组
c++·笔记·学习·算法
浅念-4 小时前
C++ 模板初阶:从泛型编程到函数模板与类模板
c语言·开发语言·数据结构·c++·笔记·学习
Bear on Toilet4 小时前
BFS_FloodFill_46 . 腐烂的橘子问题
数据结构·c++·算法·leetcode·宽度优先
橘色的喵5 小时前
一个面向工业嵌入式的 C++17 Header-Only 基础设施库
c++·嵌入式·工业·基础库·head-only
stripe-python5 小时前
十二重铲雪法(上)
c++·算法