I would disagree with this assessment of the strong never empty guarantee provided by variant2. [snip] Or do you disagree with my judgement that the never empty guarantee is not of much use?
This one.
I agree that this is how you could observe the valueless state. But I also claim that such code (I wait to be convinced otherwise) has already more serious problems than observing the valueless state on variant. You typically do not want your objects to outlive the stack unwinding. And if you do, for globals, you want to provide a transactional-like guarantee. Leaving such objects in "valid but unspecified states" is a design bug.
My issue with std::variant is that it needlessly does not conform to the usual assignment and emplacement guarantees of other standard library types. For example, resizing a std::vector implements the strong guarantee that state will be restored to before the resize if an exception is thrown in the middle of the resize. Boost.Optional implements the strong guarantee for assignment, but not for emplacement (which I think it should, but fair enough that emplacement generally has the basic guarantee. You might note that Outcome deliberately omits an emplacement modifier). See https://www.boost.org/doc/libs/1_69_0/libs/optional/doc/html/boost_optional/... Thus I would hold that, unless there is a *very* good reason why not, so should std::variant propagate the strong guarantee where it is able to do so. To my knowledge, there is no good reason that is does not, as proven by Peter's variant2.
Or am I wrong?
I suppose it depends on whether you consider variant a sort of container with responsibilities or not. I would say it is.
My position (until I see examples that will convince me otherwise) is that if a programmer observes the valueless state in a variant, then the programmer is doing something wrong with handling exceptions.
My issue is that it is a point of unintended failure that need not exist. The committee decided that exception throws during assignment or emplacement ought to create a trap state which renders the variant always throwing exceptions on use thereafter. I can see the logic, but it is wrong in my opinion. The variant should be put back into the state it was in beforehand, in my opinion. (Personally speaking, I find the double buffering a step too far. I remember debating this with Anthony Williams a few years ago at ACCU. I think that if double buffering is necessary, then you weaken your guarantees to basic, and you provide a constexpr bool for static asserting when the guarantees are basic or strong. In any case, I find the valueless by exception state to be an abomination, it should never have been allowed, it litters the code with potential throw paths none of which aid codegen)
You are contrasting boost::variant2 with std::variant, but the design space I see is more than just either of them. If std::variant is wrong (which I tend to agree with) it does not immediately imply that boost::variant2is right. Another alternative that Peter indirectly suggested is that it is UB if you try to observe the valuelsess state in std::variant. In this case some usages after a throw are banned, but the model still guarantees the never emptiness.
You're right that it doesn't mean variant2 is right. And I do have some issues with it as it is currently, which I have already covered in enough detail here. I feel far more strongly about propagation of triviality than I do about double buffering. I only have a weakly held disagreement with double buffering. It stems mainly from my belief that the compile time extra cost is not worth it for supporting pathological types (i.e. ones without noexcept move constructors). But that's a belief, not a fact, I have no empirical evidence to prove my belief. Niall