pt., 1 mar 2019 o 17:41 Niall Douglas via Boost <boost@lists.boost.org> napisaĆ(a):
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.
Hold on. Most standard library types *do not* provide strong exception safety guarantee on assignment. It is only the subset of STL containers that are implemented as pointers, such as std::vector or std::list. But consider std::array, which has to store its elements directly. It has no way of providing strong guarantee on assignment. Or consider std::pair: pair<string, string> p1 {"Niall", "Douglas"}; pair<string, string> p2 {"Peter", "Dimov"}; try { p1 = p2; } catch(...) {} If the assignment of p2.second() throws, you end up with unintended person {"Peter", "Douglas"}. The same with nearly every aggregate you might use in the program: struct Person { string firstName, lastName; }; The generated assignment only provides *basic* exception safety guarantee. Most of the types we use only provide basic exception safety guarantee in assignment. But it is usually not a problem, because in a program that correctly handles exceptions all objects that cause exceptions are immediately destroyed in stack unwinding. variant2 also only provides *basic* exception safety guarantee: you can be assigning a variant containing type B to a variant containing type A and end up with variant containing type C. Here's an example: https://wandbox.org/permlink/AObFiUKgeXIEiXQa My point is, you can only observe the valueless state in variant if you work with objects that threw from a basic-guarantee operation. But if you do this, you already have mess in your program regardless if you are using variants or not. Nobody so far has shown an example when they are using (other than resetting or destroying) objects in a "valid but unspecified state" in a correct program. Regards, Andrzej
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
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost