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 <
> 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;
> 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
f(x); // are you aware that you are copying a vecotor?
Maybe no-one uses expected<T> as function parameter, but consider this:
// 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
>> 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
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?