Boost logo

Boost :

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


pt., 1 mar 2019 o 10:59 Peter Dimov via Boost <boost_at_[hidden]>
napisał(a):

> Andrzej Krzemienski wrote:
> > So, does the following recommendation correctly capture the design goals
> > for boost::variant2?
> >
> > If you require the never-empty guarantee (and accept the costs) use
> > boost::variant2.
> >
> > If you do not require the never empty guarantee use std::variant.
>
> Kind of, but as written this implies that std::variant has no costs, which
> is not true. The checks for valueless do carry a cost. Each visit(), for
> example, starts with `if(valueless) throw`, which is not necessary in
> variant2.
>
> > Also, I am not entirely satisfied with the reply, "those who want this
> > guarantee". Could you, or anyone else, give me a real-world use case
> where
> > a never-empty guarantee is needed, but a strong exception guarantee is
> > not?
>
> My reply was unsatisfactory because I was really not looking forward to
> rehashing the arguments against singular states. Singular state is bad
> when
> a result of two-phase construction (which is why we no longer use
> two-phase
> construction), it's bad when a result of exception (which is why we don't
> use destroy-only exception safety but basic exception safety), it's bad
> when
> a result of default initialization of built-in types (but we can do
> nothing
> about it), and it's bad when a result of a move (which is why move
> semantics, as originally specified, do not put the moved-from object in a
> singular state.)
>
> Singular states introduce implicit "is_valid" preconditions on all your
> normal functions, and partition the program into two worlds, a normal
> world
> where no object is singular, and an "exceptional" world where objects may
> be
> singular. It's _possible_ to program in this way, but it's not fun,
> because
> world #2 may never call into world #1 under penalty of undefined behavior,
> and singular objects are never to enter world #1, because this sets up a
> delayed explosion.
>
> If you avoid singular states, this removes all these implicit "is_valid"
> preconditions, which removes the partitioning and collapses the two worlds
> back into one; "type 2" code can call "type 1" code and nothing undefined
> will happen.
>
> Now in principle, for the specific case of move, it's possible and sound
> to
> specify it to leave the object in a singular state, provided that you only
> ever move from objects that are about to be immediately destroyed. But
> that's not the approach that was taken. In this timeline, move does not
> leave objects in a singular state, so there is no requirement to only ever
> use it on objects that are about to be destroyed.
>
> For variant specifically, the guarantee that variant<X, Y> can only ever
> either hold an X, or hold a Y, simplifies the specification of all code
> taking variant<X, Y>, because it's not required to document what happens
> in
> the event of the variant not holding X or Y.
>
> "Never empty" is somewhat a misnomer, because variant2 can be empty, you
> just have to request it explicitly: variant<monostate, X, Y>. Of course
> then
> you have to explicitly handle the possibility of the variant being empty.
> If
> the variant is an implementation detail of some component of yours that
> has
> behavior X' when in state X and behavior Y' when in state Y, you would
> need
> to decide what happens when the variant is empty. Do you emulate X' or Y',
> or does the component behave in a third way, E'? Up to you, but undefined
> behavior is probably not acceptable, unless you introduce a singular state
> for your component.
>
> What are the alternatives? One is to do what std::variant does and try to
> have the cake and eat it too. Have the empty state, but don't acknowledge
> it
> in the interface as equal to other states, throw an exception instead.
> This
> is a bit like sweeping the problem under the carpet, it allows people to
> pretend that the variant delivers the "never empty" guarantee and program
> as
> if it did, whereas it doesn't.
>
>
Thanks for a very long reply. I am sorry if my questions look like trying
to repeat the same discussion again. In fact my goal is only to
*understand* your rationale behind your design choices; not to argue with
them.

In your reply you mostly address the issue of a "singular state" in
general, but I was hoping for an answer specific to variant. I think the
two are different. A general "singular state" is dangerous (I
whole-heartedly agree with you here) because users can create this state by
using a default constructor or moving from the object. But this is not the
case for variant: both default-constructed state and the moved-from state
is not "valueless", even in std::variant. The only way to get to a
valueless state is to trigger an exception from a move constructor. And it
seems to me that when this happens, the only reasonable choice for the user
is to either reset or destroy the variant. Therefore the practical
implication of having such "variant-speciffic singular state" seems to me
not noticeable.

If you were serious about your convictions you would make accessing a
> valueless variant undefined behavior, which avoids the valueless checks
> but
> see above about singular states and time bombs.
>

Yes: it makes a lot of sense to me to make an attempt to access the value
of a valueless variant an undefined behavior. I do not associate this
decision with the problems of types with singular states in general,
because there is no easy way to obtain the valueless state in variant; and
it seems to me that if we get an exception that puts the variant into this
state, and the user makes an effort to intercept the exception and do
things with the variant, the user must surely be doing something wrong.

So, let me restate my question in more precise terms. Is there a real-world
use case for a variant (as opposed to any other UDT), given that
default-constructed and moved-from variant is not valueless, where it makes
the difference if the variant is guaranteed not to be valueless but does
not provide a strong exception safety guarantee?

Regards,
Andrzej


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