std::optional can finally hold a reference
std::optional<T&> has been ill-formed since C++17. The standard explicitly said so, and every standard library enforced it with a static_assert. The reason was not technical difficulty but an unresolved design question. Should opt = other rebind the reference to point somewhere new, or assign through it and change the referenced object? Both answers are defensible, and the committee could not settle it. The whole feature was left out rather than shipped with a coin flip.
So we all wrote T* instead, and added a comment explaining that null means absent. That works, but it loses something real. A raw pointer says nothing about ownership. It also says nothing about whether null is expected, and it composes badly with the rest of the optional-shaped API surface.
C++26 settles the question. std::optional<T&> is legal, and assignment rebinds (P2988).
The demo binds an optional<std::string&> to a local, writes through it, and shows the change landing in the original object:
has=true value=hello
s now = hello world
empty has=false
The specialization holds a pointer internally. It is exactly as cheap as the T* you were writing, with no allocation and no extra indirection. What you gain is the interface:
has_value()andvalue_or()- the monadic
and_thenandtransform - a type whose name states the intent
Rebinding, and why it is the right call
The rule to remember is that assignment rebinds:
std::optional<int&> o = a;
o = b; // o now refers to b; a is untouched
*o = 42; // writes through to b
This is the same choice std::reference_wrapper makes, and it is what makes the type usable as a value. You can put it in a container, reassign it in a loop, and return it from a function. Assign-through would have made optional<T&> behave unlike every other optional in the library. There, o = x replaces the contained thing rather than mutating it.
One honest caveat about availability. The demo above compiles and runs on GCC 16.1. But libstdc++ does not yet define __cpp_lib_optional_ref, so a feature test for it will report the feature as missing even where it works. If you are writing portable code, guard on a compiler version for now rather than the macro. Switch to the macro once the libraries catch up.
The obvious use is the lookup that may not find anything. Return std::optional<Value&> instead of a pointer, and the caller gets a type that says “maybe there, do not own it” without a comment.
Sources: P2988R12 “std::optional<T&>” · cppreference: std::optional.