std::optional specialization for reference types
When n3406 (revision #2 of the proposal) was discussed, some committee members were uncomfortable with optional references. In n3527 (revision #3), the authors decided to make optional references an auxiliary proposal, to increase the chances of getting optional values approved and put into what became C++14. While optional didn't quite make it into C++14 for various other reasons, the committee did not reject optional references and is free to add optional references in the future should someone propose it.
There is indeed something that has reference to maybe existing object semantics. It is called a (const) pointer. A plain old non-owning pointer. There are three differences between references and pointers:
- Pointers can be null, references can not. This is exactly the difference you want to circumvent with
std::optional
. - Pointers can be redirected to point to something else. Make it const, and that difference disappears as well.
- References need not be dereferenced by
->
or*
. This is pure syntactic sugar and possible because of 1. And the pointer syntax (dereferencing and convertible to bool) is exactly whatstd::optional
provides for accessing the value and testing its presence.
Update:
optional
is a container for values. Like other containers (vector
, for example) it is not designed to contain references. If you want an optional reference, use a pointer, or if you indeed need an interface with a similar syntax to std::optional
, create a small (and trivial) wrapper for pointers.
Update2: As for the question why there is no such specialization: because the committee simply did opt it out. The rationale might be found somewhere in the papers. It possibly is because they considered pointers to be sufficient.
The main problem with std::optional <T&>
is — what should optRef = obj
do in the following case:
optional<T&> optRef;
…;
T obj {…};
optRef = obj; // <-- here!
Variants:
- Always rebind —
(&optRef)->~optional(); new (&optRef) optional<T&>(obj)
. - Assign through —
*optRef = obj
(UB when!optRef
before). - Bind if empty, assign through otherwise —
if (optRef) {do1;} else {do2;}
. - No assignment operator — compile-time error "trying to use a deleted operator".
Pros of every variant:
Always rebind (chosen by boost::optional and n1878):
- Consistency between the cases when
!optRef
andoptRef.has_value()
— post-condition&*optRef == &obj
is always met. - Consistency with usual
optional<T>
in the following aspect: for usualoptional<T>
, ifT::operator=
is defined to act as destroying and constructing (and some argue that it must be nothing more than optimization for destroying-and-constructing),opt = …
de facto acts similarly like(&opt)->~optional(); new (&opt) optional<T&>(obj)
.
- Consistency between the cases when
Assign through:
- Consistency with pure
T&
in the following aspect: for pureT&
,ref = …
assigns through (not rebinds theref
). - Consistency with usual
optional<T>
in the following aspect: for usualoptional<T>
, whenopt.has_value()
,opt = …
is required to assign through, not to destroy-and-construct (seetemplate <class U> optional<T>& optional<T>::operator=(U&& v)
in n3672 and on cppreference.com). - Consistency with usual
optional<T>
in the following aspect: both haveoperator=
defined at least somehow.
- Consistency with pure
Bind if empty, assign through otherwise — I see no real benefits, IMHO this variant arises only when proponents of #1 argue with proponents of #2, however formally it's even more consistent with the letter of requirements for
template <class U> optional<T>& optional<T>::operator=(U&& v)
(but not with the spirit, IMHO).No assignment operator (chosen by n3406):
- Consistency with pure
T&
in the following aspect: pureT&
doesn't allow to rebind itself. - No ambiguous behavior.
- Consistency with pure
See also:
- Let’s Talk about std::optional<T&> and optional references.
- Why Optional References Didn’t Make It In C++17.