$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
Subject: Re: [boost] [variant2] Need rationale for never-empty guarantee
From: Niall Douglas (s_sourceforge_at_[hidden])
Date: 2019-03-02 18:25:29
>> 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.
You see, I would consider any union-based storage as in the same
category. By definition union-based storage must contain a "pointer" to
the correct way to interpret that union-based storage.
>
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:
And which is the same in Outcome. Aggregate storage has the property
that aborting moves mid-stride leaves the aggregate partially moved-to
or moved-from. Everybody expects that, which is why it is important to
preserve that behaviour (and Outcome does).
But union-based storage does NOT have that property. There is any one of
<T ...>. I expect T...'s triviality, noexcept, and all other guarantees
to be propagated where possible. Unless there is a very good reason not to.
> struct Person
> {
> string firstName, lastName;
> };
>
> The generated assignment only provides *basic* exception safety guarantee.
I wouldn't look at it that way. I would say that an aggregate is like an
array. Each type in the array/aggregate provides its own guarantees. The
aggregate does not interfere with each type's guarantees. Each member is
"atomic" in this regard.
> 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.
Global state is left in an intermediate state. At the "high water mark"
where the exception throw occurred.
I get your point that global state ought to be explicitly unwound. But
there are idempotent global state designs where interruption at any
point does not matter. A trap-state variant just doesn't fit into such
designs, so I can't use std::variant. That annoys me.
> 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
I just don't get why variant2 would set a state of C when it had state
A, and setting state B failed. It should have spotted the lack of common
noexcept move, employed double buffers, and alternated between them such
that state A is untouched should setting state B fail.
If it's going to do weirdness like setting state C out of the blue, then
better dispose entirely any double buffered implementation as not adding
value.
Niall