Re: [boost] [outcome] Possible extensions/changes to std::experimental::expected
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
::expected(expected ); // E is convertible from G I see that the other constructor is also useful
EXPLICIT expected
::expected(expected); // T is convertible from U and so
EXPLICIT expected
::expected(expected); // 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
2. variadic expected
I suggested this already in revision 2 of the Expected standard proposal.
expected
should be something like variant but with some syntactic sugar. expected
could be EXPLICIT convertible from expected 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
f(); T f() throw (E1, ...., En);
We can consider that this is bad, as we have abandoned it.
Anyway, I think that expected
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
In this way we could have
template <class T>
using option = expected
; 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
; // and possibly none. template <class T>
using outcome = expected
; I believe we would need the syntactic sugar as expected
> should have a lot of user code noise.
In addition it will be less efficient than variant
E.g. expected
> should be convertible from unexpected_type<G> as soon a one of the Ek is convertible from G. I believe variant
is not convertible from variant 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
2017-05-25 9:44 GMT+02:00 Vicente J. Botet Escriba via Boost < boost@lists.boost.org>:
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
::expected(expected ); // E is convertible from G I see that the other constructor is also useful
EXPLICIT expected
::expected(expected); // T is convertible from U and so
EXPLICIT expected
::expected(expected); // 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
-> expected (E->G) stands for a function that takes an E and return a G.
2. variadic expected
I suggested this already in revision 2 of the Expected standard proposal.
expected
should be something like variant but with some syntactic sugar. expected
could be EXPLICIT convertible from expected 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
f(); T f() throw (E1, ...., En);
We can consider that this is bad, as we have abandoned it.
Anyway, I think that expected
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
are return code, so maybe we could to make their life easier.
In this way we could have
template <class T>
using option = expected
; 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
; // and possibly none. template <class T>
using outcome = expected
; I believe we would need the syntactic sugar as expected
> should have a lot of user code noise. In addition it will be less efficient than variant
. E.g. expected
> should be convertible from unexpected_type<G> as soon a one of the Ek is convertible from G. I believe variant
is not convertible from variant 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?
Found it. It was through a defect report: http://cplusplus.github.io/LWG/lwg-defects.html#2756
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
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
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.
So, more specifically, I understand that you propose the following: - Default constructor works: no T or no E is construced (similar what outcome<> does) - You can assing to and and destroy such an objetc (similar what outcome<> does) - You will probably need to add an observer function that checks for this singular state, like `is_singular()`. If not for anything else it would be used for assisting the static analysis tools. (again, similar what outcome<> does) - other observer functions (has_value(), value(), has_error(), error()) cause UB when `is_singular() == true. (this is the only difference from outcome<>) Did I understand your intentions correctly?
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?
I think that ordering expected<> is very rare, and has no intuitive
semantics: two elements with no value but different error are equivalent or
not? If a user is determined to store them in maps, or sort them, let her
provide the ordering predicate manually:
std::set
5. About implicit conversion from T
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. We have in some way that expected
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.
It may depend on a personal programming stye. Mine is also in favour of explicitness. I even consider conversion from T to optional<T> to be dangerous.
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?
E may be `std::error_code`. People sometimes writhe `catch (std::exception const& e)` to mean "catch evrything (but skip boost::thread_interrupted)". This would stop working if we start throwing non-std::exceptions-s.
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?
Probably not: the configuration should be reduced to minimum (whatever this means).
6. Implicit conversion from E outcome::expected
outcome::expected
is implicitly convertible from E when there is no risk of ambiguity. std::expected
is implicitly convertible from unexpected<E>. What people thing we should have? In Boost? in the standard
Can this additional wrapping into unexpected<E> cause negative performance effects? Regards, &rzej;
Le 25/05/2017 à 13:18, Andrzej Krzemienski via Boost a écrit :
2017-05-25 9:44 GMT+02:00 Vicente J. Botet Escriba via Boost < boost@lists.boost.org>:
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
::expected(expected ); // E is convertible from G I see that the other constructor is also useful
EXPLICIT expected
::expected(expected); // T is convertible from U and so
EXPLICIT expected
::expected(expected); // 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
-> expected (E->G) stands for a function that takes an E and return a G.
2. variadic expected
I suggested this already in revision 2 of the Expected standard proposal.
expected
should be something like variant but with some syntactic sugar. expected
could be EXPLICIT convertible from expected 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
f(); T f() throw (E1, ...., En);
We can consider that this is bad, as we have abandoned it.
Anyway, I think that expected
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
are return code, so maybe we could to make their life easier. In this way we could have
template <class T>
using option = expected
; 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
; // and possibly none. template <class T>
using outcome = expected
; I believe we would need the syntactic sugar as expected
> should have a lot of user code noise. In addition it will be less efficient than variant
. E.g. expected > should be convertible from unexpected_type<G> as soon a one of the Ek is convertible from G. I believe variant
is not convertible from variant 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?
Found it. It was through a defect report: http://cplusplus.github.io/LWG/lwg-defects.html#2756 Thanks.
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
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
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.
So, more specifically, I understand that you propose the following:
- Default constructor works: no T or no E is construced (similar what outcome<> does) I was not aware of this. Do you know that from this reference documentation?
constexpr outcome () noexcept(is_nothrow_default_constructible) Default constructor. Sorrty, I couldn't interpret it as you. But, if it is the case, yes, as outcome does.
- You can assing to and and destroy such an objetc (similar what outcome<> does)
I don't catch what you mean here. Are you referring to the conversion between expected with convertible value type and error types?
- You will probably need to add an observer function that checks for this singular state, like `is_singular()`. If not for anything else it would be used for assisting the static analysis tools. (again, similar what outcome<> does)
No. There will be no such observer. This is essential. There is no visible empty state. Or do you consider that chrono::duration should tell you if it was initialized or not? The user know if it is initialized or not, but there is no tool to check it. This could be against safe programming, but we are here in C++ and we should provide first the raw tools and then build on top of them when we want more.
- other observer functions (has_value(), value(), has_error(), error()) cause UB when `is_singular() == true. (this is the only difference from outcome<>)
Not the only one. outcome fixes its error types. I'm proposing an extension to the current expected so that it can take care of the Outcome use cases and needs.
Did I understand your intentions correctly?
Not completly. I don't want an observer that tells me, attention you have not initialized your variable, or you have moved from. This is essential.
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?
I think that ordering expected<> is very rare, and has no intuitive semantics: two elements with no value but different error are equivalent or not? If a user is determined to store them in maps, or sort them, let her provide the ordering predicate manually:
std::set
m1; std::set m2; The two predicates could ship with the standard.
This point was open since the beginning. After all a lot of people wanted to remove it from optional. I will live with or without. Removing it will reduce a lot the proposal text and the implementation, but I suspect that some one will need to define those predicates.
5. About implicit conversion from T
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. We have in some way that expected
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.
It may depend on a personal programming stye. Mine is also in favour of explicitness. I even consider conversion from T to optional<T> to be dangerous.
Glad to hear that.
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?
E may be `std::error_code`. People sometimes writhe `catch (std::exception const& e)` to mean "catch evrything (but skip boost::thread_interrupted)". This would stop working if we start throwing non-std::exceptions-s.
Except if the user uses E that are std::exceptions ;-) I don't think your argument is important. The user must know what he does.
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?
Probably not: the configuration should be reduced to minimum (whatever this means). So if we don't want to configure and don't want to throw E we have just to throw bad_expected_access<E>.
Note that in order to throw the exception stored inside a exception_ptr you need some kind of customization or you need to hard code this type :( This was the case until I removed the exception_ptr as default Error and as a particular case.
6. Implicit conversion from E outcome::expected
outcome::expected
is implicitly convertible from E when there is no risk of ambiguity. std::expected
is implicitly convertible from unexpected<E>. What people thing we should have? In Boost? in the standard
Can this additional wrapping into unexpected<E> cause negative performance effects? No that I'm aware of. What do you have in mind? Do you believe that your tagged_bool can cause some negative performance effects? If t is the case, we have a problem while raising the level of abstraction and safety.
Best, Vicente
4. About comparisons
Outcome doesn't implement comparisons between Outcomes. He pretend that we don't need them.
I don't pretend we don't need them. I say that people who store Expected in an associative map need their head examined. And if they really, really want to, they can supply a custom comparison function to the map. We don't need default ordering operators if it means losing implicit conversion and creating yet more needless boilerplate for end users.
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?
Throwing types without a vtable is punished badly in optimisation by some compilers e.g. GCC. Before you ask, no I don't know why. Throwing types not derived from std::exception I think that ship sailed from C++ a long time ago. I think WG21 would vote against it. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
Niall Douglas wrote:
I don't pretend we don't need them. I say that people who store Expected in an associative map need their head examined. And if they really, really want to, they can supply a custom comparison function to the map. We don't need default ordering operators if it means losing implicit conversion and creating yet more needless boilerplate for end users.
+1
Le 25/05/2017 à 16:55, Niall Douglas via Boost a écrit :
4. About comparisons
Outcome doesn't implement comparisons between Outcomes. He pretend that we don't need them. I don't pretend we don't need them. I say that people who store Expected in an associative map need their head examined. And if they really, really want to, they can supply a custom comparison function to the map. We don't need default ordering operators if it means losing implicit conversion and creating yet more needless boilerplate for end users. You and Andrzej have almost convinced me ;-)
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? Throwing types without a vtable is punished badly in optimisation by some compilers e.g. GCC. Before you ask, no I don't know why.
This is a compiler problem, isn't it? I don't know how I could take this argument for the standard.
Throwing types not derived from std::exception I think that ship sailed from C++ a long time ago. I think WG21 would vote against it.
Maybe you are right. We have 1/2 of possibilities ;)
If a had a expected
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.
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.
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
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.
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
Le 26/05/2017 à 03:00, Gavin Lambert via Boost a écrit : 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.
(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
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? Thanks for the feedback, Vicente
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?
Andrzej Krzemienski wrote:
Maybe no-one uses expected<T> as function parameter, but consider this:
expected
g() { vector<T> v; // try to populate v; return v; // are you expecting a copy elision here? }
This should move (subject to a clarification in the core language though.)
2017-05-26 14:22 GMT+02:00 Peter Dimov via Boost
Andrzej Krzemienski wrote:
Maybe no-one uses expected<T> as function parameter, but consider this:
expected
g() { vector<T> v; // try to populate v; return v; // are you expecting a copy elision here? } This should move (subject to a clarification in the core language though.)
Is this some recent addition? Regards, &rzej;
Andrzej Krzemienski wrote:
2017-05-26 14:22 GMT+02:00 Peter Dimov via Boost
: Andrzej Krzemienski wrote:
Maybe no-one uses expected<T> as function parameter, but consider this:
expected
g() { vector<T> v; // try to populate v; return v; // are you expecting a copy elision here? } This should move (subject to a clarification in the core language though.)
Is this some recent addition?
Moving on return is C++11, but it only kicked in when the types were the same. In C++17, the wording is different and it seems that the intent is for the above to also move (assuming that expected<T> has a constructor taking T&&.)
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 :(
This is fine. It's a compile time error. Writing f(expected<T>(x)) eliminates the ambiguity.
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
); f(x); // are you aware that you are copying a vecotor?
If people fail to use std::move, they should always assume a copy. Except when returning from a function. f(std::move(x)) would do the right thing here I believe.
Maybe no-one uses expected<T> as function parameter, but consider this:
expected
g() { vector<T> v; // try to populate v; return v; // are you expecting a copy elision here? }
This is definitely a concern for C++ 14 (not 17). But I think end users get this problem with implicit conversion - it helps that recent clangs have a rich set of warnings when you do an inefficient return of a stack allocated object. So I don't think it as much a problem as others think.
Even if we wanted that expected
is valid only if T is different from E, I believe the explicitness has added value.
Throughout AFIO v2, I not only **always** return via make_valued_result
or make_errored_result, I found myself specifying the return type too
e.g. make_valued_result
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 :( This is fine. It's a compile time error. Writing f(expected<T>(x)) eliminates the ambiguity. Right, so being explicit from the beginning will avoid the need for change.
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
); f(x); // are you aware that you are copying a vecotor? If people fail to use std::move, they should always assume a copy. Except when returning from a function.
f(std::move(x)) would do the right thing here I believe. So the implicit conversion of x will generate a copy. Would an explicit conversion help here? not too much, but at least the user will see that
Le 26/05/2017 à 17:57, Niall Douglas via Boost a écrit : the vector is passed as parameter to an expected before being passed to f.
Maybe no-one uses expected<T> as function parameter, but consider this:
expected
g() { vector<T> v; // try to populate v; return v; // are you expecting a copy elision here? } This is definitely a concern for C++ 14 (not 17).
Are you sure copy elision in C++17 takes this case into account?
But I think end users get this problem with implicit conversion - it helps that recent clangs have a rich set of warnings when you do an inefficient return of a stack allocated object. So I don't think it as much a problem as others think. It will be great if you added in the documentation how do you solve the reported warnings ;-) I suspect that the solution is to access the stored value, isn't it?
Even if we wanted that expected
is valid only if T is different from E, I believe the explicitness has added value. Throughout AFIO v2, I not only **always** return via make_valued_result or make_errored_result, I found myself specifying the return type too e.g. make_valued_result (s); But imposing that on users so they have no choice ... that I don't agree with for a Boost library. Here we disagree as we already know. I am much less concerned with a STL type admittedly, there I'd live with always explicit construction.
Not me, I'm really concerned ;-) Vicente
Maybe no-one uses expected<T> as function parameter, but consider this:
expected
g() { vector<T> v; // try to populate v; return v; // are you expecting a copy elision here? } This is definitely a concern for C++ 14 (not 17). Are you sure copy elision in C++17 takes this case into account?
I am not up to date with C++ 17 yet. But assuming http://en.cppreference.com/w/cpp/language/return#Notes is correct: "If expression is an lvalue expression and the conditions for copy elision are met, or would be met, except that expression names a function parameter, then overload resolution to select the constructor to use for initialization of the returned value is performed twice: first as if expression were an rvalue expression (thus it may select the move constructor or a copy constructor taking reference to const), and if no suitable conversion is available, overload resolution is performed the second time, with lvalue expression (so it may select the copy constructor taking a reference to non-const). The above rule applies even if the function return type is different from the type of expression (copy elision requires same type)" ... then yes, copy elision in C++ 17 takes this case into account.
But I think end users get this problem with implicit conversion - it helps that recent clangs have a rich set of warnings when you do an inefficient return of a stack allocated object. So I don't think it as much a problem as others think.
It will be great if you added in the documentation how do you solve the reported warnings ;-)
I don't think the right place for telling people how to write warnings-free C++ is Boost documentation. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
2017-05-26 3:00 GMT+02:00 Gavin Lambert via Boost
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.
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.
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.
Maybe this is just subject to one's private opinion, but I do not agree
that expected
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.
You may be right here. Even thoug I believe there is no algebraic argument to back it up, it is a practical choice.
(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. 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). 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.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman /listinfo.cgi/boost
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).
A static assert should prevent construction with a T convertible to or
from an error_code_extended or a std::exception_ptr. As
error_code_extended explicitly constructs from error_code,
outcome
On 27/05/2017 01:16, Niall Douglas wrote:
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). A static assert should prevent construction with a T convertible to or from an error_code_extended or a std::exception_ptr. As error_code_extended explicitly constructs from error_code, outcome
should not compile.
Sorry, I probably should have said "since *I assume* outcome
Sorry, I probably should have said "since *I assume* outcome
is legal". I can imagine some scenarios where that might be useful (perhaps as part of some error translation code which wants to return a modified value but can also fail) but I suppose that would complicate the constructors a bit so I can understand why it'd be disallowed, and it should be rare enough not to really bite anyone.
(Although tagged constructors to resolve that ambiguity wouldn't be an issue if you could only create an errored outcome with a helper like make_unexpected...)
It's disallowed purely to avoid SFINAE on constructors and thus improve compile times. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
On 30/05/2017 03:07, Niall Douglas wrote:
Sorry, I probably should have said "since *I assume* outcome
is legal". I can imagine some scenarios where that might be useful (perhaps as part of some error translation code which wants to return a modified value but can also fail) but I suppose that would complicate the constructors a bit so I can understand why it'd be disallowed, and it should be rare enough not to really bite anyone.
(Although tagged constructors to resolve that ambiguity wouldn't be an issue if you could only create an errored outcome with a helper like make_unexpected...)
It's disallowed purely to avoid SFINAE on constructors and thus improve compile times.
That's why I mentioned tagged constructors. No SFINAE needed. Something like: template<typename T> class result { public: struct error_tag {}; outcome(const T& val) { set_value(val); } outcome(error_code err, error_tag) { set_error(err); } //... }; template<typename T> result<T> make_error(error_code err) { return result<T>(err, result<T>::error_tag()); } You could even make the error_tag constructor private and make_error a friend to ensure that make_error is the only possible way to produce an errored result. (Or keep the constructor public but make error_tag private; same effect.) With this setup, there is no ambiguity when T is error_code -- user code with "return err" will return the error code as the value, and with "return make_error(err)" will return it as an error. I'm not sure I would actually *recommend* this design, I'm just pointing out that it's possible and removes the ambiguity without any SFINAE.
Le 30/05/2017 à 00:59, Gavin Lambert via Boost a écrit :
On 30/05/2017 03:07, Niall Douglas wrote:
Sorry, I probably should have said "since *I assume* outcome
is legal". I can imagine some scenarios where that might be useful (perhaps as part of some error translation code which wants to return a modified value but can also fail) but I suppose that would complicate the constructors a bit so I can understand why it'd be disallowed, and it should be rare enough not to really bite anyone.
(Although tagged constructors to resolve that ambiguity wouldn't be an issue if you could only create an errored outcome with a helper like make_unexpected...)
It's disallowed purely to avoid SFINAE on constructors and thus improve compile times.
That's why I mentioned tagged constructors. No SFINAE needed. Something like:
template<typename T> class result { public: struct error_tag {};
outcome(const T& val) { set_value(val); } outcome(error_code err, error_tag) { set_error(err); } //... };
template<typename T> result<T> make_error(error_code err) { return result<T>(err, result<T>::error_tag()); }
You could even make the error_tag constructor private and make_error a friend to ensure that make_error is the only possible way to produce an errored result. (Or keep the constructor public but make error_tag private; same effect.)
With this setup, there is no ambiguity when T is error_code -- user code with "return err" will return the error code as the value, and with "return make_error(err)" will return it as an error.
I'm not sure I would actually *recommend* this design, I'm just pointing out that it's possible and removes the ambiguity without any SFINAE. This is is someway what the Expected proposal proposes :) See of unexpect_t, make_unexpected and the make_expected_from_error factories.
Vicente Vicente
It's disallowed purely to avoid SFINAE on constructors and thus improve compile times.
That's why I mentioned tagged constructors. No SFINAE needed. Something like:
If one were to use tagged construction, one would almost certainly use std::variant's set (http://en.cppreference.com/w/cpp/utility/variant/variant) or a subset thereof. I believe I am right in saying that resolving overloads with a single parameter is constant time in the same way that resolving function names is. But resolving overloads with two or more parameters is no longer constant time. The other thing is that a large minority of my userbase say they want implicit construction from both T and E, and they are happy for T and E to be non-constructible into one another to get it. I don't think it a large cost to give that to them when E is always an error_code type thing. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
participants (5)
-
Andrzej Krzemienski
-
Gavin Lambert
-
Niall Douglas
-
Peter Dimov
-
Vicente J. Botet Escriba