Boost logo

Boost :

Subject: Re: [boost] [outcome] Possible extensions/changes to std::experimental::expected
From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2017-05-26 12:13:43


2017-05-26 7:16 GMT+02:00 Vicente J. Botet Escriba via Boost <
boost_at_[hidden]>:

> Le 26/05/2017 à 03:00, Gavin Lambert via Boost a écrit :
>
>> On 25/05/2017 19:44, Vicente J. Botet Escriba wrote:
>>
>>> 3. uninitialized default constructed expected<T,E>
>>>
>>
>> I've already stated my opinion on this elsewhere.
>>
> Point taken.
> I'll ask the committee to consider removing the default constructor.
>
>>
>> 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.
>>>
>>
>> I agree with Niall -- putting one directly in an ordered collection is
>> bizarre and if someone really wants to do that then it should be left up to
>> them to define what ordering makes sense to them. operator< should
>> absolutely not be implemented and I would hesitate before providing any
>> free standard ordering methods.
>>
> As I said elsewhere, you (all) have convinced me. I'll suggest to remove
> them on the next revision.
>
>>
>> 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.
>>>
>>
>> Implicit construction does not imply is-a, it implies is-superset-of.
>> Which is true, expected<T,E> is a superset of T. As the intent is to
>> naturally convey T as a wrapped return value of a method, the implicit
>> construction allows "return some_t_value;" as a simplified syntax and the
>> most natural one. Forcing users to use "return
>> make_expected(some_t_value);" instead would be a disservice and
>> discouragement, I think.
>>
> I like the explicitness of make_expected. I'll ask the committee to
> consider explicit construction as a safer design.
> I understand people like implicit constructors until they find that they
> don't want them here or there.
>
> void f(optional<T>);
> T x;
> f(x);
>
> Later on add
> void f(expected<T>);
> T x;
> f(x); // ambiguous :(
>
> So T is a subset of two sets and then we need to be explicit. Been
> explicit from the beginning avoids surprises.
>

This is just a compile-time surprise, so this is not all that bad. But you
may get a run-time surprise

vector<T> x;
void f(expected<vector<T>>);

f(x); // are you aware that you are copying a vecotor?

Maybe no-one uses expected<T> as function parameter, but consider this:

expected<vector<T>> g()
{
  vector<T> v;
  // try to populate v;
  return v; // are you expecting a copy elision here?
}

>
>
>
>
>> (In order to imply is-a then there must be a reverse conversion operator
>> from expected<T,E> to T, and that definitely should not exist, not even as
>> explicit.)
>>
>> The caveat (and the reason make_unexpected is required) is where T and E
>> are compatible, eg. expected<int, int> or expected<double, int> etc. In
>> this case there is a possibility that someone intending to return an error
>> value might forget to use make_unexpected and end up with code that
>> compiles but is not correct. Requiring explicit make_expected does
>> mitigate this case but I'm not sure it's worth the hassle.
>>
>> No, I wanted to have make_expected/make_unexpected from the beginning as
> we are explicit in Haskell
>
> Either T U = Left T | Right U
>
> but people wanted that expected<T> should behave as much as possible as a
> T. I don't think this is a good thing. I don't mind to be explicit in this
> case as it is clearer and more robust.
>
>
> Outcome uses a different mitigation by restricting the possible types of
>> E, rendering the above case very unlikely (although not impossible, since
>> outcome<error_code> is legal).
>>
> Outcome T U = T | U
>
> and that implies that both are different.
>
> Even if we wanted that expected<T,E> is valid only if T is different from
> E, I believe the explicitness has added value.
>
>
>> 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?
>>>
>>
>> At least where E happens to be std::error_code it would be nice if it
>> threw std::system_error, since that is the exception designed for such
>> things. Otherwise I have no opinion.
>>
> Are you saying that you are for hard coding this mapping?
> What other hard coded mappings do we neeed?
> exception_ptr -> the contained exception
>
> Others?
>

We are discussing the context when someone calls `rslt.value()` when
`rslt.has_value() == false`, right? Before going too far, I would like to
know whether we want to consider has_value() as a precondition to value(),
or do we want value() to have wide contract?


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