Boost logo

Boost :

Subject: Re: [boost] [variant2] Need rationale for never-empty guarantee
From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2019-03-01 23:07:16

pt., 1 mar 2019 o 17:41 Niall Douglas via Boost <boost_at_[hidden]>

> >> 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
> 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:

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.


> > 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:

Boost list run by bdawes at, gregod at, cpdaniel at, john at