How is std::optional never "valueless by exception"?

optional<T> has one of two states:

  • a T
  • empty

A variant can only enter the valueless state when transitioning from one state to another if transitioning will throw - because you need to somehow recover the original object and the various strategies for doing so require either extra storage1, heap allocation2, or an empty state3.

But for optional, transitioning from T to empty is just a destruction. So that only throws if T's destructor throws, and really who cares at that point. And transitioning from empty to T is not an issue - if that throws, it's easy to recover the original object: the empty state is empty.

The challenging case is: emplace() when we already had a T. We necessarily need to have destroyed the original object, so what do we do if the emplace construction throws? With optional, we have a known, convenient empty state to fallback to - so the design is just to do that.

variant's problems from not having that easy state to recover to.


1 As boost::variant2 does.
2 As boost::variant does.
3 I'm not sure of a variant implementation that does this, but there was a design suggestion that variant<monostate, A, B> could transition into the monostate state if it held an A and the transition to B threw.


std::optional has it easy:

  1. It contains a value and a new value is assigned:
    Easy, just delegate to the assignment operator and let it deal with it. Even in the case of an exception, there will still be a value left.

  2. It contains a value and the value is removed:
    Easy, the dtor must not throw. The standard library generally assumes that for user-defined types.

  3. It contains no value and one is assigned:
    Reverting to no value in the face of an exception on constructing is simple enough.

  4. It contains no value and no value is assigned:
    Trivial.

std::variant has the same easy time when the type stored does not change.
Unfortunately, when a different type is assigned it must make place for it by destroying the previous value, and then constructing the new value might throw!

As the previous value is already lost, what can you do?
Mark it as valueless by exception to have a stable, valid though undesirable state, and let the exception propagate.

One could use extra space and time to allocate the values dynamically, save the old value somewhere temporarily, construct the new value before assigning or the like, but all those strategies are costly, and only the first always works.


"valueless by exception" refers to a specific scenario where you need to change the type stored in the variant. That necessarily requires 1) destroying the old value and then 2) creating the new one in its place. If 2) fails, you have no way to go back (without undue overhead unacceptable to the committee).

optional doesn't have this problem. If some operation on the object it contains throws an exception, so be it. The object is still there. That doesn't mean that the object's state is still meaningful - it's whatever the throwing operation leaves it in. Hopefully that operation has at least the basic guarantee.