pt., 1 mar 2019 o 11:57 Peter Dimov via Boost
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. :-)
Thanks for the correction. Although it does not change the reasoning in this thread: valueless state can only be observed during stack unwinding (or when the unwinding was stopped too soon).
(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.
This is technically true. I argue that an attempt to observe the states of local objects, during stack unwinding is something wrong on itself: you do not want to do this. Therefore being careful not to call ordinary "world #1" function should come automatically from following other good practices: do not fiddle with local objects during stack unwinding: just let them be destroyed.
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.
Yes, when you use vec.assign(b, e); you may end up in an unintended state. In that case you can only reasonably reset or destroy the vector.If you try to count its elements, there is no UB, but at the same time you are doing something wrong. What do you want to do with such count? In case of a vector, in this state you can do more things with it. But this is a guarantee that adds no value to anyone. This is at least what I am trying to investigate with this thread. And also note that if I wanted to get from state A to state B and due to an exception I ended up in a valid but unspecified state, I cannot call just any operations that were valid on A and B: std::vector<X> v(10), w(10); v[9]; // valid w[9]; // valid try{ v.assign(w.begin(), w.end()); } catch(...) { v[9]; // maybe UB }
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.
No: it is normal that in "valid but unspecified state" you can do less than other states: E.g., I can call `*p = 0` when I know that pointer `p` points to an object within its lifetime, but I cannot call this operation safely when a `p` is in a "valid but unspecified state".
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.
I would still want to hear from you or other people if they ever use a variant (or in fact any other object with assignment that offers basic exception safety guarantee) in this way: An exception is thrown, but I stop the stack unwinding so that the object is still in scope, and I do (intentionally) anything else than resetting it. Does anyone do it? Regards, Andrzej