Boost logo

Boost :

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


pon., 4 mar 2019 o 00:54 Gavin Lambert via Boost <boost_at_[hidden]>
napisał(a):

> On 3/03/2019 07:25, Niall Douglas wrote:
> > 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.
>
> While I agree that switching to an apparently unrelated state is
> surprising, it could be anticipated and "solved" by always using
> monostate as the first type where the types might throw on move. (And
> perhaps variant could issue a warning or error if you try to do otherwise?)
>
> It seems reasonable to require a monostate state to exist unless using
> types that can guarantee that it is never needed. And with it "in their
> face" in the type declaration, consumers of the variant should be less
> likely to forget about handling it, which is one of the problems with
> valueless_by_exception.
>
> I find it a lot more common that people writing move
> constructors/assignment forget to declare it noexcept than it actually
> being exception-prone, so having more diagnostics ("if you really meant
> to do that, add monostate") rather than silently degrading performance
> seems like a good thing.
>

To summarize a bit, there are four mechanisms for assuring that implicit
"valueless" state never occurs:
1. Just assume that operations involved never throw (this works for some
types)
2. Make the "valueless" state explicit by using monostate.
3. Apply some tricks with move constructors or default constructors to
bring back *any* state other than "valueless".
4. Use double buffering

variant2 tries to cleverly select the best approach for a given set of
types. While it is obvious that option 4 comes with the cost, it is worth
noting that option 3 is also not free. Using option 2 changes the contract
to the extent that you are in fact creating a different type with different
invariant.

This might be a good default for some applications, but programmers often
want to make this decision consciously and explicitly.

(On a peripherally related note, why did variant introduce monostate
> instead of reusing nullopt_t?)

nullopt_t was a compromise: the Committee didn't feel comfortable with
introducing a generic boost::none_t with this name with well defined
semantics (comparability, ordering, interaciotns with nullptr_t) that soon
in the process, so we provided something that was supposed to be a
temporary solution, a tag (like std::piecewise_construct) whose only
purpose is to indicate an intention to initialize std::optional to a state
of not containing a value. Now std::monostate has taken the role of
boost::none_t. It would make sense to use it in std::optional:

std::optional<int> oi = std::monostate{};

Regards,
Andrzej


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk