Andrzej Krzemienski wrote:
But this is not the case for variant: both default-constructed state and the moved-from state is not "valueless", even in std::variant. The only way to get to a valueless state is to trigger an exception from a move constructor.
This is not true, by the way (although it should have been). std::variant also goes valueless on emplace when the construction throws. I have argued against this behavior in https://wg21.link/p0308r0, which also contains other interesting things. :-) (It was even less true before P0308 was partially adopted.)
And it seems to me that when this happens, the only reasonable choice for the user is to either reset or destroy the variant.
I can only repeat what I said about singular states. Stack unwinding in this case is an example of "world #2" code, which can observe a valueless variant. This means that it must be careful to never ever call an ordinary "world #1" function on a variant object. There's really nothing variant-specific here; the same argument applies to any function having the basic exception safety guarantee. When you assign to a vector, and the assignment throws, you can only reasonably reset or destroy the vector. But we don't make it destroy-only. And if you do have a type that is destroy-only, it breaks basic exception safety everywhere it's used. If you assign vector<variant> and it throws, the vector is now destroy-only.
Yes: it makes a lot of sense to me to make an attempt to access the value of a valueless variant an undefined behavior. I do not associate this decision with the problems of types with singular states in general, because there is no easy way to obtain the valueless state in variant;
It doesn't have to be easy to be a problem. In fact, easy is better, because it happens more frequently and is therefore tested.