Boost logo

Boost :

Subject: Re: [boost] [review] **NEXT WEEK** Review of Outcome (starts Fri-19-May)
From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2017-05-15 08:15:06


2017-05-15 1:35 GMT+02:00 Peter Dimov via Boost <boost_at_[hidden]>:

> Niall Douglas wrote:
>
> The major refinement of outcome<T>, result<T> and option<T> over
>> expected<T, E> is that you **never** get undefined behaviour when using
>> their observers like you do with expected<T, E>. So when you call .error()
>> for example, you always get well defined semantics and behaviours.
>>
>
> I fully agree with this design decision of yours, by the way. In fact I
> consider that a defect in expected<>.
>
> you get a C++ exception thrown of type monad_error(no_state).
>>
>
> As a side note, it would be nice from my point of view if you eradicate
> these last remaining references to 'monad' in the public interface and make
> that outcome_error (resp. outcome_errc, outcome_category.)
>
> outcome<Foo> found; // default constructs to empty
>> for(auto &i : container)
>> {
>> auto v = something(i); // returns a result<Foo>
>> if(v) // if not errored
>> {
>> found = std::move(v); // auto upconverts
>> break;
>> }
>> }
>> if(!found)
>> {
>> die();
>> }
>>
>
> OK, let's go with that. Why not construct 'found' initially to contain
> some error, instead of being empty? You can even define a special errc
> constant to denote an empty outcome.
>
> Sure, this will not throw the exception in your earlier example which
> accessed v.error() when !v, but is the exception really necessary there?
>
> What I'm driving at is that these result types are conceptually (T|E) and
> the empty state could just be a special case of E.
>
> Or, in more general terms, I feel that there's still much extra weight
> that can be stripped off (not in terms of sizeof, but in terms of the
> interface.)
>

I think there is no good solution here. The expectation of the caller, as
you say, is that the function either returns a `T` or an error (explanation
why if failed to produce a `T`). But on the callee side you often need the
empty state, because the flow would often be like this:

```
vector<outcome<T>> os (20); // no value or error to assign yet

for (auto const& e : some_collection)
if (cond(e))
  os[i].set_value();
else
  os[i].set_exception();
```

And in some cases, you cannot immediately rewrite the code in order to
avoid default construction. You do not want to return an "empty" object,
but you need this value temporarily, end there may be no good value of `E`
to use.

I think Niall's choice is a practical one: if you must compromise the
interface anyway on one or the other side of the function, at least
optimize for performance: implementing the "empty" state comes for free.

Regards,
&rzej;


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