1
If you’re a regular reader of my blog, you know I’ve been sharing what I learn about new C++ language and library features ever since C++20. You probably also read my CppCon 2025 Trip Report. And this post is where the two come together. At CppCon I attended a great talk by Steve Downey about std::optional. Steve is the father of optional references—he co-authored P2988R12 with Peter Sommerlad. Let’s start with a little history. By now — at the end of 2025 — even C++17 feels like history. That’s when std::optional was introduced, giving us a way to represent “maybe a value” with value semantics, instead of relying on pointers. But std::optional in C++17 (and later) couldn’t hold references — unless you wrapped them in std::reference_wrapper. C++26 finally fixes this gap. What is std::optional? std::optional has three key characteristics: Unlike std::optional, it is not an owning type. It simply refers to an existing object. It provides both reference and value-like semantics. Internally, it behaves like a pointer to T, which may also be nullptr. This last point is interesting: while optional can be seen as variant, optional is closer to variant. In practice, it’s a safer alternative to a non-owning raw pointer. Does this mean one less reason to use raw pointers? Well, owning raw pointers have been discouraged since C++11. Non-owning raw pointers are still common for expressing “I don’t own this.” With C++26, optional might replace many of those cases. You’ll still need to check engagement, but at least it’s more expressive than sprinkling nullptr checks everywhere. Key considerations The design of std::optional aimed for the least surprising and least dangerous behavior. Let’s walk through some of the decisions. Assign or Rebind? Consider this snippet (from Steve Downey’s CppCon talk): 1 2 3 4 5 6 Cat fynn; Cat loki; std::optional maybeCat1; std::optional maybeCat2{fynn}; maybeCat1 = fynn; maybeCat2 = loki; What should these assignments do? Should they copy objects or rebind references? If they were true assignments, maybeCat2 = loki; would copy loki into fynn. That would have been surprising and error-prone. Instead, the committee decided that operator= for optional always rebinds the reference. At the end of the snippet: maybeCat1 refers to fynn maybeCat2 now refers to loki, not fynn. This design makes the behavior consistent and avoids accidental copies. What about make_optional()? make_optional() returns an optional, not optional. Even if you pass a reference, it still creates an owning optional. This is intentional: allowing make_optional would lead to dangling references. In practice, make_optional was mostly used in test cases. With C++26, you’ll need to construct optional directly if you want an optional reference. It’s also worth noting that the main usage of make_optional are tests cases. The question of constness Should optional model shallow or deep const? For a const optional, should operator*() and operator->() yield T& or const T&? The designers chose shallow constness: dereferencing a const optional still gives you a non-const T&. If you need deep constness, you can use optional. The least dangerous value_or What about value_or? For optional, it returns a T. For optional, the safest design turned out to be the same: value_or returns a T (by value). This avoids surprising reference semantics, and it supports common use cases like providing a literal fallback: 1 auto name = maybeName.value_or("Anonymous"s); Steve also mentions in the paper future proposals for free functions like reference_or, value_or, or_invoke, and yield_if for all optional-like types. Stay tuned — I’ll probably cover those here soon. Conclusion std::optional fills an important gap in the language. It gives us a safer and more expressive way to model maybe-a-reference, reducing our reliance on raw pointers and reference wrappers. It’s another step toward making code both more readable and less error-prone — two things I’m always happy to see in modern C++. Connect deeper If you liked this article, please hit on the like button, subscribe to my newsletter and let’s connect on Twitter!
You must log in or register to comment.
Phew, having to use reference_wrapper sucked. I hope this will be a bit more ergonomic.
This allows to use methods of optional (c++23 added a couple of nice ones), as opposed to raw pointer. It seems to be obvious, but there is no mention of it in the discussion behind the link.