CppCon 2025 学习:std:optional<T&> Optional Over References
一、Optional 的历史
Optionals were first proposed for C++ in 2005.
Optional<T>, where T is constrained not to be a reference, was added in 2017.
Optionals for lvalue references were voted in at the Sofia meeting in June, 2025
理解:
- 2005 年:最初提出了 Optional 的概念,用于表示一个可能有值也可能没有值的对象。
- 2017 年:
Optional<T>正式加入 C++17 标准,但 T 不能是引用类型。- 即
std::optional<int>是合法的,但std::optional<int&>当时不被允许。
- 即
- 2025 年 Sofia 会议:针对 左值引用 的 Optional(
optional<T&>)被正式投票通过。
总结:
- Optional = “可能存在的值”
- 引用类型的 Optional 需要特殊处理,因为引用没有默认构造和拷贝语义。
二、早期背景与关注点
Speaker notes: early history, Boost.Optional and reference specialization
理解:
- 早期历史:
- Boost.Optional 是 C++ 社区最初实现 Optional 的库
- 早期尝试 specialize 对引用的 Optional
- 问题:
- Optional 对引用的行为容易产生陷阱,尤其是赋值和生命周期问题(state-dependent assignment)
举例:
int x = 42; std::optional<int&> o; // 空 optional o = x; // 赋值后 o 持有 x 的引用 x = 100; // 通过 optional 访问时值变了
三、关键提案文档
- N1878: A Proposal to Add an Utility Class to Represent Optional Objects
- P1175R0: A Simple and Practical Optional Reference for C++
理解:
- N1878:最早期 Optional 提案
- P1175R0:为 Optional 引用提出了简洁实用的实现方案
- 目标:在不破坏已有 Optional 的前提下,支持
optional<T&>
- 目标:在不破坏已有 Optional 的前提下,支持
四、C++20 的尝试
Std:Optional reproposed reference support for C++20, which was not adopted
理解:
- C++20 曾经尝试为 Optional 添加引用支持
- 最终没有通过,原因:
- 引用的赋值行为可能依赖于 Optional 是否有值(state-dependent)
- 设计复杂性增加
五、后续发展
picked up the torch again in 2023, revision 9 design approved by Library Evolution Working Group, R13 adopted
理解:
- 2023 年:重新提出 optional 引用支持的提案
- Revision 9:设计被 Library Evolution Working Group 批准
- R13:最终被采纳为标准
六、Optional 的 Range 支持
In 2024, the proposal to make optional a range
- P3168R0: Give Std:Optional Range Support
- P1683R0: References for Standard Library
- P2988R0
理解:
- 2024 年:提出让 Optional 支持 range 接口
- 不再单独区分 0 或 1 的元素
- Optional 引用的实现和
views::maybe测试用例被用来验证新接口
- 意义:
- Optional 与 Range 更好整合
- 同时通过实际实现获得社区反馈
- 成为 Beman 库 的基础之一
可视化理解:
optional<T>→range supportrange of 0 or 1 element(s) \text{optional<T>} \xrightarrow{\text{range support}} \text{range of 0 or 1 element(s)} optional<T>range supportrange of 0 or 1 element(s)
七、总结与关键点
- Optional 对引用的标准化
- 从 2005 到 2025,经历了多轮提案
- C++17 中
optional<T>不支持引用 - 2025 年 Sofia 会议通过
optional<T&>
- 设计难点
- 引用的生命周期
- 状态依赖赋值行为
- 提案文档
- N1878、P1175R0、P1683R0、P3168R0、P2988R0
- Range 支持
- Optional 变成长度为 0 或 1 的范围
- 统一接口,便于组合和泛型算法
- 实际效果
- Optional 引用 + Range 接口
- 提高标准库一致性
- 为 Beman 库提供了可测试的实现和反馈
一、为什么花了很长时间?
The core of the difficulty has been that references are not values and types containing a reference do not have value semantics.
References do not fit comfortably in the C++ type system.
理解:
- 引用不是值
- C++ 中
int&、T&是引用类型,本质上是别名,不拥有对象本身。 - Optional 是值类型(value type),需要具备 值语义(value semantics):
- 拷贝、赋值、移动应该保持一致行为
- 拥有明确的生命周期
- C++ 中
- 引用不符合值语义
- 例如
std::optional<T&>拷贝或赋值时,不能真正拷贝引用所指的对象,只能拷贝引用本身
- 例如
困难的根源:类型系统里引用和值类型不一致
二、为什么不能直接用指针?
The core value semantic type that also has reference semantics is a pointer,
but pointers have underconstrained and unsafe semantics.
理解:
- 指针(
T*)有值语义,并可以指向对象 - 但是指针有潜在问题:
- 可能为空(
nullptr) - 可能悬空(dangling pointer)
- 对指针的操作不安全
- 可能为空(
所以直接用指针替代引用无法满足 Optional 的安全设计要求
三、背后的设计讨论
The long discussion has been a proxy for what reference semantic types should
look like in value semantic types in the standard library,
particularly for "sum" types, like expected and variant.
理解:
- 标准化 Optional<T&> 的讨论本质上是:
- 如何把 引用语义(reference semantics) 安全地嵌入到 值语义类型(value semantic type) 中
- 特别是对于 sum types(和类型选择相关的类型):
std::expected<T, E>std::variant<Ts...>
- 目标:在保证值语义安全的前提下,允许引用类型存在
四、Optional 快速回顾
An owning type, with value semantics, with one additional out-of-band value.
理解:
- Optional 是一个拥有对象(owning type)
- 具有值语义(可以复制、赋值、移动)
- 并且多了一个额外的“空”状态(out-of-band value)
- 用于表示没有值
数学上可以用 代数类型 来表示:
ALGEBRAICALLY
T + 1
- 意思是:类型 T + 一个额外的“空”状态
- C++ 实现上常用:
std::variant<T, std::monostate>
std::monostate用作“没有值”的占位符
五、总结
- 核心难点:
- 引用不是值类型
- 值语义类型不能直接包含引用
- 指针不是好方案:
- 虽然值语义 + 引用语义,但不安全
- 最终目标:
- 让 Optional、expected、variant 等 sum type 支持引用语义
- 同时保持值语义安全
- 代数表示:
Optional<T>≅T+1≅std::variant<T, std::monostate> \text{Optional<T>} \cong T + 1 \cong \text{std::variant<T, std::monostate>} Optional<T>≅T+1≅std::variant<T, std::monostate>
一、核心用例 (Core Use Case)
int size;
if (optional<int> s = readConfigValue("Size")) {
size = *s;
} else {
size = 0;
}
return size;
理解:
readConfigValue("Size")返回一个optional<int>- 如果配置中有 “Size”,就返回
optional<int>包含该值 - 如果没有,返回空
optional
- 如果配置中有 “Size”,就返回
if (optional<int> s = ...)语句用于判断是否有值*s解引用获取值- 否则用默认值
0
核心思想:Optional 可以安全地表示“有值或无值”状态
二、C++26 范围版本 (Range Version)
int size = 0;
for (int s : readConfigValue("Size")) {
size = s;
}
return size;
理解:
- C++26 中,
optional<T>支持 范围 (range) 语义- 可以直接用于
for循环 - 如果 optional 有值,循环执行一次
- 如果 optional 为空,循环不执行
- 可以直接用于
- 优点:
- 省去了显式
if判断 - 更简洁、表达式语义清晰
- 省去了显式
数学上可以理解为:
optional<T>∼零或一元素集合 T 或 空 \text{optional<T>} \sim \text{零或一元素集合 } { T \text{ 或 空} } optional<T>∼零或一元素集合 T 或 空
- 所以可以用 集合迭代 语义
三、默认 Optional 参数 (Default Optional Parameter)
constexpr int optParam(int a, optional<int> b = {}) {
if (b) {
return a + *b;
}
return a;
}
const auto t1 = optParam(3); // b 为空
const auto t2 = optParam(3, 4); // b = 4
static_assert(t1 != t2);
理解:
b是一个optional<int>参数- 默认值为空
{} - 可以传递实际的值,例如
4
- 默认值为空
- 调用示例:
optParam(3)→b为空 → 返回a = 3optParam(3, 4)→b有值 → 返回a + b = 7
static_assert(t1 != t2);- 编译期验证两种情况的结果不同
优点:
- Optional 可以安全地作为 默认参数
- 可以用 单一函数 处理有值和无值情况
四、总结
| 用例 | C++26 改进 | 解释 |
|---|---|---|
| 核心用例 | if(optional) |
显式判断是否有值 |
| 范围用例 | for(int s : optional) |
Optional 可迭代,空 optional 循环不执行 |
| 默认参数 | optional<int> b = {} |
可以作为函数默认参数,简化函数调用 |
数学视角:
optional<T>∼T + 1(一个值或空) \text{optional<T>} \sim \text{T + 1} \quad (\text{一个值或空}) optional<T>∼T + 1(一个值或空)
- 在迭代语义下:
for x∈optional<T>⇒0次或1次循环 \text{for } x \in \text{optional<T>} \Rightarrow 0 \text{次或1次循环} for x∈optional<T>⇒0次或1次循环
一、optional<T&> 快速概览
A non-owning type,
with reference and value semantics,
with one additional value representing the empty state.
理解:
- 非拥有类型(Non-owning)
optional<T&>不拥有引用对象本身,只是引用已有对象
- 具有引用和值语义(Reference & Value Semantics)
- 可以像普通引用一样访问对象
- 也可以像普通 optional 一样判断是否存在值
- 有一个特殊值表示空状态(Empty State)
- 类似
optional<T>的nullopt - 用于表示引用不存在
- 类似
数学表示:
optional<T&>∼引用对象∪空 \text{optional<T\&>} \sim { \text{引用对象} } \cup {\text{空}} optional<T&>∼引用对象∪空
二、核心用例:查找并修改 Map 中的元素
传统方式(Today)
auto i = map.find("one");
auto j = map.find("two");
auto k = map.find("three");
assert(i->second == 1);
assert(j->second == 2);
assert(k == map.end());
理解:
- 使用
map.find返回迭代器- 如果找到了,迭代器指向元素
- 如果没找到,返回
map.end()
- 核心问题:
- 对未找到的元素访问需要判断
iterator != map.end() - 编码比较繁琐
- 对未找到的元素访问需要判断
使用 optional<int&> 的方式
optional<int&> i = findInMap("one");
optional<int&> j = findInMap("two");
optional<int&> k = findInMap("three");
EXPECT_TRUE(*i == 1);
EXPECT_TRUE(*j == 2);
EXPECT_TRUE(!k);
// 修改值
*(findInMap("one")) = 3;
EXPECT_TRUE(*i == 3);
理解:
findInMap("one")返回optional<int&>- 如果找到返回引用
- 如果未找到返回空
- 优点:
- 不再需要检查迭代器是否为
end() - 可以直接解引用修改值
- 更加安全和简洁
- 不再需要检查迭代器是否为
数学表示:
optional<int&>={引用对象如果找到空如果未找到 \text{optional<int\&>} = \begin{cases} \text{引用对象} & \text{如果找到} \\ \text{空} & \text{如果未找到} \end{cases} optional<int&>={引用对象空如果找到如果未找到
三、可选引用作为函数参数
void doSomething(std::string const& data,
optional<Logger&> logger = {}) {
for (auto l : logger) {
l.log(data);
}
return;
}
理解:
logger是optional<Logger&>- 默认值为空
{} - 表示可以没有 logger
- 默认值为空
for (auto l : logger)- 遍历可选引用
- 如果有值,循环一次
- 如果为空,循环不执行
- 优点:
- 替代传统指针参数(
Logger* logger = nullptr) - 语义更清晰,避免空指针错误
- 可以安全地调用
log(data)
- 替代传统指针参数(
数学上理解:
optional<T&>∼0 或 1 个元素集合 \text{optional<T\&>} \sim \text{0 或 1 个元素集合} optional<T&>∼0 或 1 个元素集合
- 用作范围(range)语义,遍历一次或不遍历
四、总结
| 用例 | 传统写法 | optional<T&> 写法 |
优势 |
|---|---|---|---|
| 查找并读取 | 迭代器判断 | optional<int&> |
简洁、安全 |
| 修改值 | 迭代器解引用 | optional<int&> |
可以直接修改值 |
| 函数参数 | 指针参数 | optional<Logger&> |
安全、可遍历、不需检查空 |
总结一句话:
optional<T&>把引用的存在/不存在状态变成了可操作的对象,既保留引用语义,又拥有 optional 的安全性和范围语义。
更多推荐



所有评论(0)