Boost logo

Boost :

Subject: Re: [boost] [outcome] Possible extensions/changes to std::experimental::expected
From: Vicente J. Botet Escriba (vicente.botet_at_[hidden])
Date: 2017-05-25 07:44:33


Hi,

I believe that this private mail I sent to Niall could concern the Boost
community and the Outcome review.
While the post concerns the proposed std::experimental::expected, this
is applicable as well to Outcome.

I've added a more points at the end and some additional information
about possible open points for std expected.

Le 24/05/2017 à 01:04, Vicente J. Botet Escriba a écrit :
>
> Hi,
>
>
> After better understanding of the goals of Boost.Outcome I would like
> to share two possible extension of std::experimental::expected
>
>
> 1. Implicit conversion when the errors are convertible and the type is
> the same.
>
> I believe this is a missing constructor.
>
> EXPLICIT expected<T, E>::expected(expected<T, G>); // E is
> convertible from G
>
> I see that the other constructor is also useful
>
> EXPLICIT expected<T, E>::expected(expected<U, E>); // T is
> convertible from U
>
> and so
>
> EXPLICIT expected<T, E>::expected(expected<U, G>); // T is
> convertible from U and E is convertible from G
>
>
We could as well have an adapt function that transforms expected error
explicitly to do error propagation

adapt : (E->G) x expected<T,E> -> expected<T,G>

(E->G) stands for a function that takes an E and return a G.

> 2. variadic expected<Y, E1, ..., En>
>
> I suggested this already in revision 2 of the Expected standard proposal.
>
> expected<Y, E1, ..., En> should be something like variant<Y, E1, ...,
> En> but with some syntactic sugar.
>
>
> expected<T, E1, ...., En> could be EXPLICIT convertible from
> expected<T, G1, ...., Gm> as far as for all Gj we find a unique Ek
> that is convertible to.
>
We can refine the subsumption relation this could take too long to the
compiler (as the previous relation has quadratic complexity as Andrzej
reported to me).
>
> I believe this will cover exactly what outcome covers and in addition
> it will let open the error types used by the user.
>
> It acts as a exception specification, which we have abandoned in C++11.
>
> expected<T, E1, ...., En> f();
>
> T f() throw (E1, ...., En);
>
>
> We can consider that this is bad, as we have abandoned it.
>
> Anyway, I think that expected<T, E1, ...., En> is a good
> generalization of the proposed outcome<T> where E1 could be none_t, E2
> could be error_code and E3 is exception_ptr.
>
>
At the end users could use variant<E, E1, ..., En> are return code, so
maybe we could to make their life easier.
>
> In this way we could have
>
> template <class T>
>
> using option = expected<T, nullopt_t>;
>
> Note that this is different to std::optional as the default is T not
> nullopt_t :(
>
I'm not proposing it. Just to say that this could be a possibility for
option.
>
> template <class T>
>
> using result = expected<T, error_code_extended>; // and possibly none.
>
> template <class T>
>
> using outcome = expected<T, none_t, error_code_extended, exception_ptr>;
>
>
> I believe we would need the syntactic sugar as expected<T,
> variant<E1,... En>> should have a lot of user code noise.
>
In addition it will be less efficient than variant<T, E1,... En>.
>
> E.g. expected<T, variant<E1,... En>> should be convertible from
> unexpected_type<G> as soon a one of the Ek is convertible from G.
>
> I believe variant<E1,... En> is not convertible from variant<G1,...
> Gm> under the conditions described above, but maybe this should be the
> case.
>
Andrzej told to me that this is already the case for optional<T>, but
I've not found yet on which paper this was added?
>
> Of course, the never empty warranties could be supported only if the
> types Ek allow it and we need to change the standard to ensure it for
> exception_ptr.
>
>
> I will not have time to implement a POC for 2, but I believe I could
> do it for 1 before Toronto. Anyway I believe it will be worth
> discussing these 2 extensions.
>
>
> Best,
>
> Vicente
>
>
> P.S. EXPLICIT above has the sense given in the standard.
>

3. uninitialized default constructed expected<T,E>

I know this could be conflictual, but there are a some advantages to
doing it.

     We don't spend time initializing with something we will assign later.

     This state corresponds to the moved-from state.

     Initializing it by default to T() is not better initializing to E().

     Is expected<Date, error_code> default constructible even if Date is
not? Currently it is not the case.

There are of course liabilities.

     We have now the possibility for uninitialized variables, but static
analysis tools will help here.

4. About comparisons

Outcome doesn't implement comparisons between Outcomes. He pretend that
we don't need them. In addition the mix of comparisons and implicit
conversion give us some surprising cases as described in Andrzej blog.
[1] A gotcha with Optional -
https://akrzemi1.wordpress.com/2014/12/02/a-gotcha-with-optional

We have that std::future is not comparable (as exception_ptr is not
comparable).

Instead of comparison we could specialize std::less<> if we want to use
them in ordered containers.

What others think?

5. About implicit conversion from T

The implicit conversion from T to expected<T,E> is a consequence of
wanting that expected<T,E> should behave like a T. But this is not
right. A expected<T,E> is not a T.

We have in some way that expected<T,E> is explicitly constructible from
E bu the use of make_unexpected(e).

If we had only and explicit constructor from T the code will be much
more uniform. Either you build it with a modified make_expected or with
make_unexpected.

In my opinion the implicit conversion from T to expected<T> is a mistake.

5. About the exception to throw

std::experimental::expected throw bad_expected_access<E>. I adopted the
design of optional and bad_optional_access

We could have as well a bad_expected_access_base class as Outcome has.

My question is why don't throw directly E?

Some are requesting a way to get a specific exception from E, but either
there is one exception that works for all and we can set it using some
kind of trait or we need to add a trait parameter to expected to the the
mapping :(

Do we really want this to be configurable?

6. Implicit conversion from E outcome::expected<T,E>

outcome::expected<T,E> is implicitly convertible from E when there is no
risk of ambiguity.

std::expected<T,E> is implicitly convertible from unexpected<E>.

What people thing we should have? In Boost? in the standard?

Vicente


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