The standard requires that, if you store a value in an std::optional
, then the value must be able to be retrieved exactly as stored. Furthermore, if an optional<T>
is engaged, you can store any T
in the optional
's value without letting the optional
know you're doing it. Like this:
optional<T> opt = T{};
auto &&val = *opt;
val = <insert value here>; //opt has no idea it has been set.
Because of this, the only valid way optional<T>
can be optimized to use certain values of T
to mean that the optional
is unengaged is if it is impossible for a user to create a T
with those values. IEEE-754 implementations of double
can assume any bitpattern, and all of them are legal (even signaling NaN).
The reason other optional types can do this is because they have an implicit agreement with the user that they will not set it to certain values. std::optional<T>
has no such agreement; any value which T
can assume can be stored and retrieved.
Now, if optional<T>::operator*
and optional<T>::value
returned some kind of proxy object rather than a direct reference to T
, then this might be possible, since the proxy could handle appropriate conversions. But even then, the standard would have to specifically state that attempting to set it to one of these values will cause the value to assume an equivalent but different object representation.