最后更新: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¶
这是整个讨论的核心死结:
两种解读:
- 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 的语义也想表达,模板参数要怎么设计?
相关主题¶
- C++17 [[nodiscard]] 属性 | C++那些事
- C++17 std::string_view | C++那些事
- C++17 PMR 内存资源 | C++那些事
- C++17 十七项高级特性 | C++那些事
- C++17 类模板参数推导 CTAD | C++那些事
- C++17 std::variant | C++那些事
- C++17 结构化绑定 | C++那些事
- C++17 模板 auto 推导 | C++那些事
- C++17 变量模板 | C++那些事