跳转至

最后更新:2026-10-13

C++17 optional_ref

本文译述自 foonathan 的博客 optional_ref discussion,原文是关于 std::optional<T&>(optional 引用)为什么最终没进标准的最精彩的一篇讨论。这个问题在 C++26 前夕又被翻出来讨论了一轮,值得把核心论点理一遍。

1.问题:std::optional 为什么编译不过?

std::optional<int>、std::optional<std::string> 都没问题,但 std::optional<int&> 直接编译报错。原因说起来有点"历史遗留":标准制定时对它的**赋值语义**没有达成共识,于是干脆在标准里写明 T 为引用类型时行为未定义。

那一个"可能为空的引用"到底有什么难的?作者提炼了四个问题:

(1)optional<T&> 和指针是不是一回事?

(2)我们真的需要 optional<T&> 吗?

(3)赋值时应该 rebind(重新绑定到新对象)还是 assign-through(写到所指对象)?

(4)它到底该不该有赋值运算符?

作者的 tl;dr 答案:不是、不需要、rebind、不该有。下面看为什么。

2.optional 引用不是指针

直觉上"可能为空的引用"就是指针,但作者指出了一个更本质的差异:

  • T& 的创建与访问都是**隐式**的:拿到引用就直接用,不需要解引用;
  • T* 的创建与访问都是**显式**的:要 &x 取地址、要 *p 解引用。

这个显式性差异是有价值的:指针提醒你"这里可能为空、可能有别名问题"。把 optional<T&> 做成处处隐式转换的"更方便的指针",恰恰丢掉了这层保护。作者的结论是:如果你要表达"可能为空",显式的 T* 反而是更好的文档。

3.rebind 还是 assign-through

这是整个讨论的核心死结:

int a = 1, b = 2;
std::optional<int&> opt = a;
opt = b;  // 问题:这句是什么意思?

两种解读:

  • rebind:opt 现在绑定到 b(引用的别名语义,int& ra = a; ra = b; 是把 b 赋给 a……等等,int& 的赋值恰恰是 assign-through!)
  • assign-through:把 b 的值写进 a(普通引用 T& 的语义)。

矛盾就在这里:如果 optional 引用想对齐 T& 的语义,赋值就该是 assign-through;但一个**可变的 optional**(可以再装别的对象)直觉上又要求 rebind。两个语义都合理,委员会吵不出结果,于是 optional<T&> 就一直没有合法化。

实践中想要 rebind 语义,社区的解法是自己造 optional_ref<T>:内部存 T*,赋值就是换指针,语义清清楚楚。C++26 的 std::optional_ref 提案走的也是这条路。

4.小结

(1)optional<T&> 不是指针的语法糖,两者在显式性上有本质分工;

(2)卡住它的是赋值语义二义性:rebind 与 assign-through 都说得通;

(3)需要"可空引用"时,显式指针或 optional_ref 都是比"合法化的 optional"更安全的选择。

最后留一个思考题:optional<T&> 的 value() 与 *opt 应该返回 T& 还是 const T&?如果捕获为 const 的语义也想表达,模板参数要怎么设计?

相关主题

评论