Boost logo

Boost :

Subject: Re: [boost] [outcome] How to drop the formal empty state
From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2017-05-26 21:37:32


2017-05-26 16:18 GMT+02:00 Niall Douglas via Boost <boost_at_[hidden]>:

> On 26/05/2017 07:41, Gavin Lambert via Boost wrote:
> > On 26/05/2017 17:33, Vicente J. Botet Escriba wrote:
> >> std::experimentall:expected<T,E>::error, doesn't throws. I don't see
> >> a use case where we want to retrieve an error without checking
> >> before. Maybe you have a case.
> >
> > Perhaps unit tests, where you're expecting an error but the code
> > unexpectedly succeeds.
>
> Very true. I have a next gen unit testing infrastructure built around
> Outcomes where UB on any of the observers would be a no-go. You can see
> the idea of it at
> https://github.com/ned14/boost.afio/blob/master/test/
> tests/file_handle_create_close/runner.cpp#L34
> and note how self describing the tables of success and failure are.
>
> > At least in terms of storage, the current implementation of empty state
> > is presumably free (it should be no more expensive to internally store a
> > variant<none_t, T, E> than a variant<T, E>). And it's currently
> > required to exist due to exception guarantees (and possible
> > noexcept(false) move constructors).
>
> As mentioned in another discussion thread, the empty state is also used
> internally as a micro-optimisation. So it would likely remain internally
> whatever the decision taken here, as a tool for making the CPU expend
> identical CPU cycles on both positive and negative branches on state.
>
> Again, if people don't like that behaviour of outcome/result to be
> equally costly on T or E branches chosen,

What does it mean tha "outcome is equally costly on T or E branches chosen"?

expected<T, E> never has an
> empty state and therefore always naturally favours the E state (because
> in .value() you check for an errored state, and if so throw an
> exception, so returning a value is usually the branch the compiler
> generates a branch to later code for).
>
> > I don't think that T should be restricted to noexcept(true)-movable
> > types only, as this prevents using it with C++03 non-POD types (that
> > have a copy constructor but lack a move constructor), which are still
> > likely to be widespread in codebases (although perhaps less common as
> > return values).
>
> I couldn't agree more about type T. But Expected does not demand nothrow
> move construction from type T, only type E. And usually most of the time
> the end user will control the source code for any type E used. It's a
> fair restriction in exchange for never empty.
>
> > Another consideration is that regardless of default construction or not
> > is that you need to decide what an expected<T, E> will contain if
> > someone moves-from it (directly). Is it now formal-empty or does it now
> > contain a moved-from-T or moved-from-E? Or does it contain a
> > moved-from-variant<none_t, T, E> (if that's different)?
>
> We never change the state unless the user asks explicitly for that. So
> if they move from an expected<T, E> and the state was an E, it remains
> an E, just a moved-from E.
>
> Resetting to empty looks attractive, but I found out the hard way it is
> a bad design decision. Code consuming a rvalue reference does not
> actually have to move anything, nothing in the C++ standard says it
> does. It's only a widely held convention that it ought to.
>

In optional<T> we deliberately don't put the moved-from object into an
empty state (which surprises many people). This is for performance reasons:
when `T` is a trivial type, `optional<T>` can be made trivially_copyable.


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