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!
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.