std::variant
can enter a state called "valueless by exception".
As I understand, the common cause of this is if a move assignment throws an exception. The variant's old value isn't guaranteed to be present anymore, and neither is the intended new value.
std::optional
, however, doesn't have such a state. cppreference makes the bold claim:
If an exception is thrown, the initialization state of *this ... is unchanged, i.e. if the object contained a value, it still contains a value, and the other way round.
How is std::optional
able to avoid becoming "valueless by exception", while std::variant
is not?
optional<T>
has one of two states:
T
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With