2017-05-26 7:16 GMT+02:00 Vicente J. Botet Escriba via Boost < boost@lists.boost.org>:
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
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
is a consequence of wanting that expected
should behave like a T. But this is not right. A expected is not a T. Implicit construction does not imply is-a, it implies is-superset-of. Which is true, expected
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
(In order to imply is-a then there must be a reverse conversion operator from expected
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
or expected 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
is legal). Outcome T U = T | U
and that implies that both are different.
Even if we wanted that expected
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?