[outcome] High level summary of review feedback accepted so far

Some reviewers have asked what form Outcome will take in the near future. The following is a high level summary of the changes already made on develop branch or will be made according to https://github.com/ned14/boost.outcome/issues logged from review feedback. I have left out some minor issues, this lists just the major changes. This should help reviewers to decide whether to recommend acceptance or rejection. If there are any questions, please do ask. Major design changes: ===================== - option<T> to be removed https://github.com/ned14/boost.outcome/issues/48 - outcome<T> and result<T> will have their empty state removed, and all observers gain narrow contracts. Default construction is disabled. https://github.com/ned14/boost.outcome/issues/44. Example: ``` result<Foo> v(make_errored_result(std::errc::invalid_argument)); assert(v.has_error()); // true assert(!v.has_value()); // true // As if Foo(reinterpret_cast<Foo &&> // (error_code_extended(std::errc::invalid_argument)); Foo f(std::move(v.value())); assert(v.has_error()); // still true assert(!v.has_value()); // still true ``` (NOTE: expected<T, E> will track whatever LEWG Expected does, but it differs from what will become result<T> by having a wide contract on .value() and narrow contracts on operator*(), .error(), operator->(). result<T> will have narrow contracts on everything, it is basically a thin wrap of std::variant<T, error_code_extended> except with strong never empty warranty) - New typedefs outcome_e<T> and result_e<T> are identical to outcome<T> and result<T> except for adding a formal empty state. Observer contract slightly widens, an attempt to use an empty object throws a bad_outcome_access exception. Implicit conversion from non-empty-capable varieties is permitted to empty-capable varieties, but not the other way round. Default construction is to **empty**. https://github.com/ned14/boost.outcome/issues/44 - New typedefs checked_outcome<T>/checked_result<T>, checked_outcome_e<T>/checked_result_e<T> are added. These mirror the editions just described, but checks and default actions occur on all observer usage so hidden reinterpret_cast<> never occurs. Implicit conversion from non-checked varieties is permitted to checked varieties, but not the other way round. https://github.com/ned14/boost.outcome/issues/47. Examples: ``` // Note result<T> implicitly converts to checked_result<T>, but not // the other way round. So we can use same make_errored_result(). checked_result<Foo> v(make_errored_result(std::errc::invalid_argument)); assert(v.has_error()); // true assert(!v.has_value()); // true // .value() throws std::system_error( // std::make_error_code(std::errc::invalid_argument)); Foo f(std::move(v.value())); ``` ``` checked_result<Foo> v(make_valued_result(Foo())); assert(!v.has_error()); // true assert(v.has_value()); // true // .error() returns a default constructed (null) error_code_extended // when result is valued to indicate "no error here" error_code_extended ec(std::move(v.error())); assert(!ec); // true ``` - The presented library self generates using lots of preprocessor based multiple #include's to stamp out finished editions of implementation. The git repo partially preprocesses this into a nearly finished edition for minimum compile time load for end users. Due to the many more varieties of outcome<T> and result<T> provided, the preprocessor machinery would be replaced with a template based machinery instead. I will look into making optional use of extern template and a static library to retain minimum compile time load, but header only usage will remain possible. Smaller design changes: ======================= - tribool stuff to go away in default configured build. https://github.com/ned14/boost.outcome/issues/22 - .get*() functions will be removed. https://github.com/ned14/boost.outcome/issues/24 - Don't include <windows.h> on winclang. https://github.com/ned14/boost.outcome/issues/39 Documentation changes: ====================== - Licence boilerplate missing on some files. https://github.com/ned14/boost.outcome/issues/38 - Reference API docs need to spell out exact preconditions, postconditions and semantics per API. https://github.com/ned14/boost.outcome/issues/26 - Reference API docs need to describe types void_rebound, *_type and raw_*_type in detail. https://github.com/ned14/boost.outcome/issues/29 - User overridable macros which throw exceptions and provide customisation points have vanished from the docs somehow. https://github.com/ned14/boost.outcome/issues/35 - Landing page and introduction to be broken up into bitesize single page chunks. https://github.com/ned14/boost.outcome/issues/41 Still to be decided: ==================== - Should *_e() varieties provide convenience .get(), .get_error(), .get_exception() which returns T, error_code_extended and std::exception_ptr by value moved from internal state, resetting state afterwards to empty? These would mirror future.get()'s single shot observers. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

- outcome<T> and result<T> will have their empty state removed, and all observers gain narrow contracts.
I disagree with the narrow contract change. Wide was correct.
I agree. But we were outvoted, and I've retained the current wide result<T> and outcome<T>, just renamed to checked_result<T> and checked_outcome<T>. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Niall Douglas wrote:
- outcome<T> and result<T> will have their empty state removed, and all observers gain narrow contracts.
I disagree with the narrow contract change. Wide was correct.
I agree. But we were outvoted,
Were we? :-)
and I've retained the current wide result<T> and outcome<T>, just renamed to checked_result<T> and checked_outcome<T>.
This isn't enough to compensate. One, checked_result is so much longer to type that this is obviously an encouragement to use unchecked by default, a questionable guideline. Two, since result/outcome are used on API boundaries, one can't just declare "we'll use checked_result" because the API you're calling isn't yours and therefore the choice of the return type isn't yours either. If it returns the unchecked result, that's what you'll receive. And it will return the unchecked result. This breaks the idiomatic use of result<T> function() noexcept; U function2() /*yesexcept*/ { auto r = function().value(); // want throw on error // ... } where you're transitioning from a noexcept lower layer that signals errors with error_code, to a higher layer that signals errors with system_error. Since this idiomatic use is one definitive aspect of the error handling philosophy that result/outcome represent, going to unchecked would be detrimental. If value/error must be unchecked, then provide checked overloads, instead of a different type. Better yet, keep value/error checked, provide unchecked overloads. This is another instance where I now see the wisdom in providing a "richer" (another man's cluttered) interface that is, in my opinion, inelegant and unnecessary; in this case operator*. Give people who want unchecked what they want, have plausible argument for calling it op* instead of value ("consistency with optional"). A bit daft if you ask me but what can one do, we were outvoted, after all, and democracy is where it's at.

and I've retained the current wide result<T> and outcome<T>, just renamed to checked_result<T> and checked_outcome<T>.
This isn't enough to compensate. One, checked_result is so much longer to type that this is obviously an encouragement to use unchecked by default, a questionable guideline.
I would agree. But well, we were outvoted. And that probably means rejection of this library, as the presented library does not implement what the majority want (yet). I would say that I expect absolutely nobody to actually type checked_optional_result<T> each and every time. What they'll do is: namespace mylib { template<class T> using result = checked_optional_result<T>; ... } Indeed AFIO already typedefs Outcome's result into its namespace to prevent me typing outcome::result all the time, which is annoying. Also remember that checked_optional_result<T> will itself be typedefed to: template<class T> using checked_optional_result = outcome_impl<T, error_code_extended, void, default_to::empty, emptiness::formal, observers::wide>;
Two, since result/outcome are used on API boundaries, one can't just declare "we'll use checked_result" because the API you're calling isn't yours and therefore the choice of the return type isn't yours either. If it returns the unchecked result, that's what you'll receive. And it will return the unchecked result.
It's only unchecked on access, so if you store the return type from the API into a checked variety (remember we allow implicit conversion from unchecked to checked, but not the other way round), then the caller gets to choose whether access is checked or not.
This breaks the idiomatic use of
result<T> function() noexcept;
U function2() /*yesexcept*/ { auto r = function().value(); // want throw on error // ... }
where you're transitioning from a noexcept lower layer that signals errors with error_code, to a higher layer that signals errors with system_error.
Yes, but surely the programmer can see that function() returns a result<T>, not a checked_result<T>. So if they want the throw on error: result<T> function() noexcept; U function2() /*yesexcept*/ { auto r = checked_result<T>(function()).value(); // want throw on error // ... } A free function called "checked()" might be nice to convert some unchecked input outcome into its equivalent checked variety. But that's just sugar.
Since this idiomatic use is one definitive aspect of the error handling philosophy that result/outcome represent, going to unchecked would be detrimental.
I agree. But we were outvoted.
If value/error must be unchecked, then provide checked overloads, instead of a different type. Better yet, keep value/error checked, provide unchecked overloads.
The reason I chose different types is because we can have the unchecked varieties call __builtin_unreachable() so static analysers trap stupid usage of the unchecked objects. Now, that doesn't rule out additional member functions. A decision needs to be made on whether to use type converting free functions like "U &&checked(T &&)" or whether to go with duplication of checked member functions instead. So, should the type system represent the checked vs unchecked semantics, or should it not? I don't know yet. I am still thinking about that design choice. It's why I hadn't mentioned it. But I am on to the problem.
This is another instance where I now see the wisdom in providing a "richer" (another man's cluttered) interface that is, in my opinion, inelegant and unnecessary; in this case operator*. Give people who want unchecked what they want, have plausible argument for calling it op* instead of value ("consistency with optional").
A bit daft if you ask me but what can one do, we were outvoted, after all, and democracy is where it's at.
I hate with a passion that choice of giving operator*() unchecked semantics. Why should it be operator*()? Why not .value_raw(), or .value_unsafe()? The latter two would be far superior, not least that if you really want narrow semantics and less safety, the programmer has type more in exchange. But as you say, democracy. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-27 18:26 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
and I've retained the current wide result<T> and outcome<T>, just renamed to checked_result<T> and checked_outcome<T>.
This isn't enough to compensate. One, checked_result is so much longer to type that this is obviously an encouragement to use unchecked by default, a questionable guideline.
I would agree. But well, we were outvoted. And that probably means rejection of this library, as the presented library does not implement what the majority want (yet).
How do you count the votes? How many votes did you count? What option did I vote for? I indicated my preference towards not having initiali empty state (default constructor), on basis that I cannot think of a use case for it. Since you are still trying to provide `optional_result`, it means you must see some use case for it. Having so many types doesn't look good to me either. I would be more comfirtable with result<> having empty state but with additional observers with narrow contract. Regards, &rzej;

I would agree. But well, we were outvoted. And that probably means rejection of this library, as the presented library does not implement what the majority want (yet).
How do you count the votes? How many votes did you count? What option did I vote for?
It's more a case of me judging where majority opinion is at, and deciding on if I can accommodate that majority opinion at the same time as preserving what I think are red line issues and design concerns. I appreciate that this philosophy of giving people what they want instead of the "correct" single vision is not popular here, but I believe in market evolution and competitive pressure. So the right design or designs would win out in time through a process of evolutionary pressure, and it's my job to select the designs to compete and how they should interoperate with one another, and then throw it onto the market to decide what is the best mix. I personally think I'll have very little use for the narrow contract varieties, but I've been convinced by the non-empty-capable vs empty-capable distinction because it means that my public API functions can specify via the type they return whether an empty return is possible or not, thus more accurately specifying their public contract. That, and the fact I can still use a receiving empty-capable variety for detecting when loops haven't found what they were looking for etc I find very compelling.
I indicated my preference towards not having initiali empty state (default constructor), on basis that I cannot think of a use case for it. Since you are still trying to provide `optional_result`, it means you must see some use case for it.
I see a lot of use for it. I also have a lot of code written which makes great use of that empty state. The fact that the empty state can never arise on its own and can only result from the programmer introducing it into a logic chain somewhere I think is something that people either get and say "that's very useful" or they don't get and say "empty states need to be removed entirely as an obvious design mistake". I am battling, sometimes, that people think the empty state has something to do with variant's valueless_by_exception(), that it arises due to an exception throw. That isn't the case in Outcome: empty is a truly third state. The fact I've chosen that non-empty-capable silently converts to empty-capable I also think is underappreciated. It enables the best of both worlds by closely aligning the type used to the exact use case. Some would say that is a lack of vision and willingness to decide on my part. I would claim it to have been my original vision for this library all along.
Having so many types doesn't look good to me either. I would be more comfirtable with result<> having empty state but with additional observers with narrow contract.
People are getting hung up on the multitude of types. They're just API personalities onto an identical implementation. They're just different ways of speaking to the same variant storage, so basically we are using the C++ type system like a pair of glasses onto the exact same view, with well defined rules for changing which glasses you're wearing as demand presents. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

On 28/05/2017 10:46, Niall Douglas wrote:
I would agree. But well, we were outvoted. And that probably means rejection of this library, as the presented library does not implement what the majority want (yet).
Just to clarify the meaning, since I'm not especially fluent in standardese: by "narrow contract" you mean "has UB if you don't include external checks", correct? I really don't like that even being an option in a type intended to improve error handling.
I personally think I'll have very little use for the narrow contract varieties, but I've been convinced by the non-empty-capable vs empty-capable distinction because it means that my public API functions can specify via the type they return whether an empty return is possible or not, thus more accurately specifying their public contract. That, and the fact I can still use a receiving empty-capable variety for detecting when loops haven't found what they were looking for etc I find very compelling.
Doesn't that make things more complicated, though? Library A uses one flavour of it and library B uses a different flavour; how do they interoperate? (I know both can coexist due to namespacing and other things, but still if one method calls the other there has to be some kind of handover of the return value.) For that matter, what happens when a method in library A (using the guaranteed-never-empty flavour) calls a method in library B (using the empty-by-default flavour)? Can they put the return value into one of their never-empty outcomes (but then what if it's empty?) or do they have to work with the might-be-empty outcome type that's foreign to their own code and way they want to do things, and thus more likely to be used incorrectly? Typedefs can sort out the differences to a certain extent, but then you need to remember to use the right one with each library (either getting unexpected behaviour or the compiler yelling at you when incorrect, depending on implementation, neither of which is particularly desirable), and there's possible interoperability issues for storing the results from multiple libraries into a single vector, for example. I know this entire discussion has proved that it's hard to find consensus on the One True Way™, but I'm inclined to believe that it may still be important to do so when trying to define a vocabulary type.
People are getting hung up on the multitude of types. They're just API personalities onto an identical implementation. They're just different ways of speaking to the same variant storage, so basically we are using the C++ type system like a pair of glasses onto the exact same view, with well defined rules for changing which glasses you're wearing as demand presents.
Perhaps this answers some of my questions above with "it just works", but perhaps also these sorts of things should be clarified in the docs? What about interop between a v1 outcome and a v2 outcome? That's no longer an identical implementation.

2017-05-29 9:23 GMT+02:00 Gavin Lambert via Boost <boost@lists.boost.org>: On 28/05/2017 10:46, Niall Douglas wrote:
I would agree. But well, we were outvoted. And that probably means
rejection of this library, as the presented library does not implement what the majority want (yet).
Just to clarify the meaning, since I'm not especially fluent in standardese: by "narrow contract" you mean "has UB if you don't include external checks", correct? I really don't like that even being an option in a type intended to improve error handling.
Outcome improves handling of run-time failures, not handling of programmer errors (bugs). Regards, &rzej;

On 28/05/2017 10:46, Niall Douglas wrote:
I would agree. But well, we were outvoted. And that probably means rejection of this library, as the presented library does not implement what the majority want (yet).
Just to clarify the meaning, since I'm not especially fluent in standardese: by "narrow contract" you mean "has UB if you don't include external checks", correct? I really don't like that even being an option in a type intended to improve error handling. If we have both narrow and wide, you can use whatever you prefer. I in
Le 29/05/2017 à 09:23, Gavin Lambert via Boost a écrit : particular will use always a narrow contract if I know the preconditions are satisfied. Vicente

On 29/05/2017 08:23, Gavin Lambert via Boost wrote:
On 28/05/2017 10:46, Niall Douglas wrote:
I would agree. But well, we were outvoted. And that probably means rejection of this library, as the presented library does not implement what the majority want (yet).
Just to clarify the meaning, since I'm not especially fluent in standardese: by "narrow contract" you mean "has UB if you don't include external checks", correct? I really don't like that even being an option in a type intended to improve error handling.
If you feel this way too, I think between me, you and Peter we now are equal in number with the other group. Of course it's not about numbers of votes, but about feeling out where the majority opinion is at.
I personally think I'll have very little use for the narrow contract varieties, but I've been convinced by the non-empty-capable vs empty-capable distinction because it means that my public API functions can specify via the type they return whether an empty return is possible or not, thus more accurately specifying their public contract. That, and the fact I can still use a receiving empty-capable variety for detecting when loops haven't found what they were looking for etc I find very compelling.
Doesn't that make things more complicated, though? Library A uses one flavour of it and library B uses a different flavour; how do they interoperate? (I know both can coexist due to namespacing and other things, but still if one method calls the other there has to be some kind of handover of the return value.)
Outcome implements well defined directional implicit conversion between flavours. It does now, and would continue to do so.
For that matter, what happens when a method in library A (using the guaranteed-never-empty flavour) calls a method in library B (using the empty-by-default flavour)? Can they put the return value into one of their never-empty outcomes (but then what if it's empty?) or do they have to work with the might-be-empty outcome type that's foreign to their own code and way they want to do things, and thus more likely to be used incorrectly?
Under what I said I would do in the high level summary, the programmer would need to manually unpack the empty-capable object, deal with the empty state if present, and construct an non-empty-capable object for return. No implicit nor explicit conversion exists between empty-capable and non-empty-capable.
Typedefs can sort out the differences to a certain extent, but then you need to remember to use the right one with each library (either getting unexpected behaviour or the compiler yelling at you when incorrect, depending on implementation, neither of which is particularly desirable), and there's possible interoperability issues for storing the results from multiple libraries into a single vector, for example.
I believe I have been persuaded to drop the checked_* separate types in favour of unchecked, but longer named, new member functions. I'll be posting a v2 high level summary later today. I don't think accumulating disparate typed results into a vector is a problem - just use the most representative type possible. Outcome deliberately implements a hierarchy of types with a most representative edition at the bottom. It can always accumulate anything from any less representative sibling type. I use this behaviour extensively in my own code.
What about interop between a v1 outcome and a v2 outcome? That's no longer an identical implementation.
My current intention is that the programmer would need to unpack and pack at such a boundary. Safest, until we know more. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Le 27/05/2017 à 02:09, Niall Douglas via Boost a écrit :
Some reviewers have asked what form Outcome will take in the near future. The following is a high level summary of the changes already made on develop branch or will be made according to https://github.com/ned14/boost.outcome/issues logged from review feedback. I have left out some minor issues, this lists just the major changes. This should help reviewers to decide whether to recommend acceptance or rejection. If there are any questions, please do ask.
Major design changes: ===================== - option<T> to be removed https://github.com/ned14/boost.outcome/issues/48
- outcome<T> and result<T> will have their empty state removed, and all observers gain narrow contracts. Default construction is disabled. https://github.com/ned14/boost.outcome/issues/44. Example:
``` result<Foo> v(make_errored_result(std::errc::invalid_argument)); assert(v.has_error()); // true assert(!v.has_value()); // true // As if Foo(reinterpret_cast<Foo &&> // (error_code_extended(std::errc::invalid_argument)); Foo f(std::move(v.value())); assert(v.has_error()); // still true assert(!v.has_value()); // still true ``` Just a question I could do for the existing library also. What has_error mean for outcome, result? is that it has an EC or that it has no value? And now that we don't have empty, what is the sens of has_error for result? Maybe, outcome should have a get_state function that returns an enum so
Thanks Niall for this summary that the user can do a switch.
(NOTE: expected<T, E> will track whatever LEWG Expected does, but it differs from what will become result<T> by having a wide contract on .value() and narrow contracts on operator*(), .error(), operator->(). result<T> will have narrow contracts on everything, it is basically a thin wrap of std::variant<T, error_code_extended> except with strong never empty warranty)
- New typedefs outcome_e<T> and result_e<T> are identical to outcome<T> and result<T> except for adding a formal empty state. Observer contract slightly widens, an attempt to use an empty object throws a bad_outcome_access exception. Implicit conversion from non-empty-capable varieties is permitted to empty-capable varieties, but not the other way round. Default construction is to **empty**. https://github.com/ned14/boost.outcome/issues/44 Okay this corresponds to what others are naming optional_outcome,
What will be the differences between result<T> and expected<T, error_code_extended>? The wide contracts for the observers? Can not we provide wide and narrow contracts or don't reuse the same name with different meaning? If we had a expected<T, E1, .., En> what will be the differences between outcome<T> and expected<T, error_code_extended, exception_ptr>? optional_result. If we had a optional_expected<T, E1, .., En> what will be the differences between result_e<T> and optional_expected<T, error_code_extended>? what will be the differences between outcome_e<T> and optional_expected<T, error_code_extended, exception_ptr>?
- New typedefs checked_outcome<T>/checked_result<T>, checked_outcome_e<T>/checked_result_e<T> are added. These mirror the editions just described, but checks and default actions occur on all observer usage so hidden reinterpret_cast<> never occurs. Implicit conversion from non-checked varieties is permitted to checked varieties, but not the other way round. https://github.com/ned14/boost.outcome/issues/47. Examples:
``` // Note result<T> implicitly converts to checked_result<T>, but not // the other way round. So we can use same make_errored_result(). checked_result<Foo> v(make_errored_result(std::errc::invalid_argument)); assert(v.has_error()); // true assert(!v.has_value()); // true // .value() throws std::system_error( // std::make_error_code(std::errc::invalid_argument)); Foo f(std::move(v.value())); ```
``` checked_result<Foo> v(make_valued_result(Foo())); assert(!v.has_error()); // true assert(v.has_value()); // true // .error() returns a default constructed (null) error_code_extended // when result is valued to indicate "no error here" error_code_extended ec(std::move(v.error())); assert(!ec); // true ```
I will need more rationale about the need of this classes and why we need to do an action while observing them. Please, could you elaborate?
- The presented library self generates using lots of preprocessor based multiple #include's to stamp out finished editions of implementation. The git repo partially preprocesses this into a nearly finished edition for minimum compile time load for end users.
Due to the many more varieties of outcome<T> and result<T> provided, the preprocessor machinery would be replaced with a template based machinery instead. I will look into making optional use of extern template and a static library to retain minimum compile time load, but header only usage will remain possible. Glad to hear you will remove the pre-processor and use just generic templates.
My view is that the generic classes do we need are expected<T, E1, ..., EN> and optional_expected<T, E1, ...En> We could limit them to 1 or 2 Error arguments if we want as this is what we need for now.
Smaller design changes: ======================= - tribool stuff to go away in default configured build. https://github.com/ned14/boost.outcome/issues/22
- .get*() functions will be removed. https://github.com/ned14/boost.outcome/issues/24
- Don't include <windows.h> on winclang. https://github.com/ned14/boost.outcome/issues/39
Documentation changes: ====================== - Licence boilerplate missing on some files. https://github.com/ned14/boost.outcome/issues/38
- Reference API docs need to spell out exact preconditions, postconditions and semantics per API. https://github.com/ned14/boost.outcome/issues/26
I will say, not only the pre-conditions, but what the function does, requires (statically) what returns, whether it throws and what, SFINAE, exception safety, what are the constraints on the template parameters, ... Otherwise we don't know what the function does and where it can be used.
- Reference API docs need to describe types void_rebound, *_type and raw_*_type in detail. https://github.com/ned14/boost.outcome/issues/29
I don't know what this is used for yet, would you need this after the refactoring?
- User overridable macros which throw exceptions and provide customisation points have vanished from the docs somehow. https://github.com/ned14/boost.outcome/issues/35
What about the use of BOOST_THROW_EXCEPTION?
- Landing page and introduction to be broken up into bitesize single page chunks. https://github.com/ned14/boost.outcome/issues/41
Still to be decided: ==================== - Should *_e() varieties provide convenience .get(), .get_error(), .get_exception() which returns T, error_code_extended and std::exception_ptr by value moved from internal state, resetting state afterwards to empty? These would mirror future.get()'s single shot observers.
We need a valid use case to introduce them and even more as member functions. In any case, these functions can be defined on to of the provided interface, and could be non-member functions, isn't it? Vicente

- outcome<T> and result<T> will have their empty state removed, and all observers gain narrow contracts. Default construction is disabled. https://github.com/ned14/boost.outcome/issues/44. Example:
``` result<Foo> v(make_errored_result(std::errc::invalid_argument)); assert(v.has_error()); // true assert(!v.has_value()); // true // As if Foo(reinterpret_cast<Foo &&> // (error_code_extended(std::errc::invalid_argument)); Foo f(std::move(v.value())); assert(v.has_error()); // still true assert(!v.has_value()); // still true ``` Just a question I could do for the existing library also. What has_error mean for outcome, result? is that it has an EC or that it has no value?
.has_error() is true if and only if the current state is an error_code_extended instance.
And now that we don't have empty, what is the sens of has_error for result?
There will be no longer an empty state in result<T>, but for generic programming I'll be retaining a .has_error() so end users can more easily swap a result<T> for a result_e<T>.
Maybe, outcome should have a get_state function that returns an enum so that the user can do a switch.
I had previously a .visit(callable) for that.
(NOTE: expected<T, E> will track whatever LEWG Expected does, but it differs from what will become result<T> by having a wide contract on .value() and narrow contracts on operator*(), .error(), operator->(). result<T> will have narrow contracts on everything, it is basically a thin wrap of std::variant<T, error_code_extended> except with strong never empty warranty)
What will be the differences between result<T> and expected<T, error_code_extended>?
The wide contracts for the observers? Can not we provide wide and narrow contracts or don't reuse the same name with different meaning?
The description above quite literally tells you the differences.
If we had a expected<T, E1, .., En> what will be the differences between outcome<T> and expected<T, error_code_extended, exception_ptr>?
I would assume an expected<T, E1, .., En> could only return a std::variant<E1, ..., En> from its .error(). I can't think what else it could do. outcome<T> continues to provide: - T& .value() - error_code_extended .error() - std::exception_ptr .exception() i.e. hard coded.
- New typedefs outcome_e<T> and result_e<T> are identical to outcome<T> and result<T> except for adding a formal empty state. Observer contract slightly widens, an attempt to use an empty object throws a bad_outcome_access exception. Implicit conversion from non-empty-capable varieties is permitted to empty-capable varieties, but not the other way round. Default construction is to **empty**. https://github.com/ned14/boost.outcome/issues/44
Okay this corresponds to what others are naming optional_outcome, optional_result.
I'm not wedded to result_e<T> etc. Ok, I'll change result_e<T> and outcome_e<T> to optional_result<T> and optional_outcome<T>. Done at https://github.com/ned14/boost.outcome/issues/44
If we had a optional_expected<T, E1, .., En> what will be the differences between result_e<T> and optional_expected<T, error_code_extended>? what will be the differences between outcome_e<T> and optional_expected<T, error_code_extended, exception_ptr>?
I am not sure what semantics you have chosen for optional_expected<>. optional_outcome<T> is exactly like outcome<T> but with an added empty state and a default constructor which constructs to empty. Otherwise identical.
- New typedefs checked_outcome<T>/checked_result<T>, checked_outcome_e<T>/checked_result_e<T> are added. These mirror the editions just described, but checks and default actions occur on all observer usage so hidden reinterpret_cast<> never occurs. Implicit conversion from non-checked varieties is permitted to checked varieties, but not the other way round. https://github.com/ned14/boost.outcome/issues/47. Examples:
``` // Note result<T> implicitly converts to checked_result<T>, but not // the other way round. So we can use same make_errored_result(). checked_result<Foo> v(make_errored_result(std::errc::invalid_argument)); assert(v.has_error()); // true assert(!v.has_value()); // true // .value() throws std::system_error( // std::make_error_code(std::errc::invalid_argument)); Foo f(std::move(v.value())); ```
``` checked_result<Foo> v(make_valued_result(Foo())); assert(!v.has_error()); // true assert(v.has_value()); // true // .error() returns a default constructed (null) error_code_extended // when result is valued to indicate "no error here" error_code_extended ec(std::move(v.error())); assert(!ec); // true ``` I will need more rationale about the need of this classes and why we need to do an action while observing them. Please, could you elaborate?
checked_optional_outcome<T> equals exactly the outcome<T> in the presented library. checked_optional_result<T> equals exactly the result<T> in the presented library. All the discussion of the presented library to date applies to the checked_*() functions.
Still to be decided: ==================== - Should *_e() varieties provide convenience .get(), .get_error(), .get_exception() which returns T, error_code_extended and std::exception_ptr by value moved from internal state, resetting state afterwards to empty? These would mirror future.get()'s single shot observers.
We need a valid use case to introduce them and even more as member functions. In any case, these functions can be defined on to of the provided interface, and could be non-member functions, isn't it?
There is an argument to make all the observers of Expected or Outcome free functions. It certainly would fit how std::begin() etc work. So: expected<T, E> foo(T()); if(has_value(foo)) ... T x(std::move(value(foo))); T y(std::move(get(foo))); ... and so on. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Niall Douglas wrote:
I would assume an expected<T, E1, .., En> could only return a std::variant<E1, ..., En> from its .error(). I can't think what else it could do.
As the foremost authority on the nonexistent expected<T, E...>, I can tell you what it does: // F shall be in E... template<class F> bool has_error() const; template<class F> F error() const; // if sizeof...(E) == 1 bool has_error() const; E1 error() const; It returns the equivalent of variant<E...> not from error(), but from unexpected<E...> unexpected() const; which allows you to expected<T, E1, E2, E3> function() { expected<U, E1, E2> e1 = function1(); if( !e1 ) return e1.unexpected(); expected<V, E3> e2 = function2(); if( !e2 ) return e2.unexpected(); return e1.value() + e2.value(); }

I would assume an expected<T, E1, .., En> could only return a std::variant<E1, ..., En> from its .error(). I can't think what else it could do.
As the foremost authority on the nonexistent expected<T, E...>, I can tell you what it does:
// F shall be in E... template<class F> bool has_error() const; template<class F> F error() const;
// if sizeof...(E) == 1 bool has_error() const; E1 error() const;
It returns the equivalent of variant<E...> not from error(), but from
unexpected<E...> unexpected() const;
which allows you to
expected<T, E1, E2, E3> function() { expected<U, E1, E2> e1 = function1(); if( !e1 ) return e1.unexpected();
expected<V, E3> e2 = function2(); if( !e2 ) return e2.unexpected();
return e1.value() + e2.value(); }
That's a second approach. A third approach could be implementing .index() and std::get<>() for expected. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Le 27/05/2017 à 18:28, Niall Douglas via Boost a écrit :
I would assume an expected<T, E1, .., En> could only return a std::variant<E1, ..., En> from its .error(). I can't think what else it could do. As the foremost authority on the nonexistent expected<T, E...>, I can tell you what it does:
// F shall be in E... template<class F> bool has_error() const; template<class F> F error() const;
// if sizeof...(E) == 1 bool has_error() const; E1 error() const;
It returns the equivalent of variant<E...> not from error(), but from
unexpected<E...> unexpected() const;
which allows you to
expected<T, E1, E2, E3> function() { expected<U, E1, E2> e1 = function1(); if( !e1 ) return e1.unexpected();
expected<V, E3> e2 = function2(); if( !e2 ) return e2.unexpected();
return e1.value() + e2.value(); } That's a second approach.
A third approach could be implementing .index() and std::get<>() for expected.
I believe, expected should implement the SumType operations when we agree on what they are. This doesn't mean that we need .index and std::get, these are specific to variant ;-) I believe that the variant interface is not the one we need to adopt for sum types, in the same way the product type interface shouldn't be the one of tuples. We have other product types (those to which structured binding applies) that I don't see how they can have the tuple interface. Maybe I'm wrong and we are able to do it, but I don't think we should. I suspect that we will have the same issue with future language sum types. Vicente

Le 27/05/2017 à 16:24, Peter Dimov via Boost a écrit :
Niall Douglas wrote:
I would assume an expected<T, E1, .., En> could only return a std::variant<E1, ..., En> from its .error(). I can't think what else it could do.
As the foremost authority on the nonexistent expected<T, E...>, I can tell you what it does:
// F shall be in E... template<class F> bool has_error() const; template<class F> F error() const;
// if sizeof...(E) == 1 bool has_error() const; E1 error() const;
It returns the equivalent of variant<E...> not from error(), but from
unexpected<E...> unexpected() const;
which allows you to
expected<T, E1, E2, E3> function() { expected<U, E1, E2> e1 = function1(); if( !e1 ) return e1.unexpected();
expected<V, E3> e2 = function2(); if( !e2 ) return e2.unexpected();
return e1.value() + e2.value(); } Yes this is the reason d'être fro the unexpected function.
I see that the implementation you have done don't allows to return it by reference, and I was wondering if the variant2::subset function (which could for usability purposes be a non-member function) couldn't return by reference. I know that this is something related to changes to the standard. There is currently a thread on C++-Core about the whether it is UB to cast a type to another with a common layout in order to access the common members. It seems that this is valid in C, but this is not anymore valid in C++. If at the end this kind of casts were valid as well in C++, I believe that we could return subset/unexpected by reference without UB. Note that a variant with less alternatives than another one has the "same shared representation". Best, Vicente

Vicente J. Botet Escriba wrote:
I see that the implementation you have done don't allows to return it by reference, and I was wondering if the variant2::subset function (which could for usability purposes be a non-member function) couldn't return by reference. ... Note that a variant with less alternatives than another one has the "same shared representation".
It doesn't. The underlying recursive unions are completely different types, and the indices don't match. But even if it were, why would I want to return by reference? I don't. Returning by reference is bad practice, it breaks encapsulation, creates lifetime problems, and so on. It's only done when one must. I don't agree with your mindset in which returning by reference is somehow desirable.

Le 03/06/2017 à 11:25, Peter Dimov via Boost a écrit :
Vicente J. Botet Escriba wrote:
I see that the implementation you have done don't allows to return it by reference, and I was wondering if the variant2::subset function (which could for usability purposes be a non-member function) couldn't return by reference. ... Note that a variant with less alternatives than another one has the "same shared representation".
It doesn't. The underlying recursive unions are completely different types, and the indices don't match. But even if it were, why would I want to return by reference? I don't. Returning by reference is bad practice, it breaks encapsulation, creates lifetime problems, and so on. It's only done when one must. I don't agree with your mindset in which returning by reference is somehow desirable.
We don't agree with returning by reference to be good or bad. No problem. You are right that the indexes will not match in general so the subset operation cannot return the same instance. We will some kind of view that rearranges the indexes :( For get my comment my comment about returning by reference. What about the non-member function, so that we don't have code as x.template subset<E2...>() and we have subset<E2...>(x) instead? Vicente

Vicente J. Botet Escriba wrote:
What about the non-member function, so that we don't have code as x.template subset<E2...>() and we have subset<E2...>(x) instead?
You're right about the need for .template here, but I'm not sure that the need to call this function would be frequent enough to justify putting it into the outer namespace (potentially std). Leaving it as a member seems an acceptable compromise to me. Do you have any comments about my proposed expected<T, E...>? Overall impression? Good idea? Bad, no reason to waste time with it?

Vicente J. Botet Escriba wrote:
What about the non-member function, so that we don't have code as x.template subset<E2...>() and we have subset<E2...>(x) instead?
You're right about the need for .template here, but I'm not sure that the need to call this function would be frequent enough to justify putting it into the outer namespace (potentially std). Leaving it as a member seems an acceptable compromise to me.
Do you have any comments about my proposed expected<T, E...>? Overall impression? Good idea? Bad, no reason to waste time with it? It is an excellent idea to implement first a never-empty variant and
Le 03/06/2017 à 18:27, Peter Dimov via Boost a écrit : then implement expected on top of it. expected<T, E...> was in the list of open points of my proposal. You have implemented it (even if still partially) :). I'll add a link to it. You have taken some deviations from the proposal that could merit discussion * missing sizeof...(E) >0 assertion * unexpected_ type is not an explicit and different type * inheritance of bad_expected_access<T> from from bad_expected_access<> :) * Additional throw_on_unexpected https://github.com/pdimov/variant2/blob/master/include/boost/variant2/expect... I suspect that this is is_move_constructible * https://github.com/pdimov/variant2/blob/master/include/boost/variant2/expect... https://github.com/pdimov/variant2/blob/master/include/boost/variant2/expect... has_error/error should be defined only if sizeof... (E) is 1 * unexpected()/error() should have a narrow contract (but this needs to be discussed in Toronto) * I know that you don't want to return by reference but unexpected can return by reference when sizeof...(E) is 1 * remap_errors seems close to what I've called adapt * https://github.com/pdimov/variant2/blob/master/include/boost/variant2/expect... return mp_with_index<mp_size<expected>>( v_.index(), [&]( auto I ) { return this->_remap_error<R>( I, f, get<I>(v_) ); }); I don't see how get<I> can work here. The same for https://github.com/pdimov/variant2/blob/master/include/boost/variant2/expect... Could explain me how the function parameter I is know at compile time? * ::then seems to be the monad::bind, could you confirm? The monadic functions were requested to be removed from the Expected proposal and managed in a general case. * Missing catch_error from the previous version of the Expected proposal. * Wondering from constructors from convertibles expected<T,E...> from expected<U, G...>. * Factories are missing * I don't know if expected<void, E...> has been implemented Thanks for working on it. Vicente P.S. While we differ on some points about narrow contracts, returning by reference I will live with the committee decision.

Vicente J. Botet Escriba wrote:
You have taken some deviations from the proposal that could merit discussion
* missing sizeof...(E) >0 assertion
expected<T> is valid. Its purpose is to make expected{ expr } valid (the equivalent of make_expected.)
* unexpected_ type is not an explicit and different type
Could be done a different type, if this simple alias is determined to not work for some reason.
* inheritance of bad_expected_access<T> from from bad_expected_access<> :)
That enables you to catch all bad_expected_access errors. Without a base, you have to enumerate every possible E, and some of them may not be known until run time (because dlopen.)
* Additional throw_on_unexpected
This translates the E types into exceptions, as I explained in the doc.
https://github.com/pdimov/variant2/blob/master/include/boost/variant2/expect... I suspect that this is is_move_constructible
Correct, copy/paste mistake.
https://github.com/pdimov/variant2/blob/master/include/boost/variant2/expect... has_error/error should be defined only if sizeof... (E) is 1
In principle. I haven't gotten this right yet, hence the static_assert. The problem is that adding a template parameter interferes with has_error<E>/error<E>.
* unexpected()/error() should have a narrow contract (but this needs to be discussed in Toronto)
Please no.
I don't see how get<I> can work here.
https://rawgit.com/pdimov/mp11/develop/doc/html/mp11.html#mp11.reference.alg...
* ::then seems to be the monad::bind, could you confirm?
It's a monadic bind, yes. I'll remove .then in favor of operator>>, as documented.
* Wondering from constructors from convertibles expected<T,E...> from expected<U, G...>.
I'm not sure yet whether to enable value conversion. Pretty sure we don't want error conversions outside remap_errors.
* Factories are missing
Since we have argument deduction now, expected{ expr } and unexpected_{ expr } can be used for the purpose, and I saw no need for separate factory functions.
* I don't know if expected<void, E...> has been implemented
Not at the moment, although I intend to add it. It will basically change all T references throughout to void.

Le 04/06/2017 à 12:53, Peter Dimov via Boost a écrit :
Vicente J. Botet Escriba wrote:
You have taken some deviations from the proposal that could merit discussion
* missing sizeof...(E) >0 assertion
expected<T> is valid. Its purpose is to make expected{ expr } valid (the equivalent of make_expected.)
But make_expected() returns expected<T, error_code> I understand that when designing expected<T, E...> we need to reconsider some functions. BTW, to what it is useful to have a expected<T> without a possible error? is expected<T> convertible to expected<T,E...>? This would be nice. Then if I make the expected constructor from T explicit we have two explicit factories one for success and one for failure (I know most of you don't like it). I don't see how expected<T, E...> could default to expected<T, error_code>? Does it mean that we need an additional template alias template <class T, class R = error_code> using expected2<T, E>; How constructor guides will play here?
* unexpected_ type is not an explicit and different type
Could be done a different type, if this simple alias is determined to not work for some reason.
expected<variant<int,string>, int, string> ? unexpected_type<E> was used for that to avoid ambiguity.
* inheritance of bad_expected_access<T> from from bad_expected_access<> :)
That enables you to catch all bad_expected_access errors. Without a base, you have to enumerate every possible E, and some of them may not be known until run time (because dlopen.)
Point taken.
* Additional throw_on_unexpected
This translates the E types into exceptions, as I explained in the doc.
I see that this is a customization point. My proposal has an open point to be able to configure the exception to throw via a traits. I'll add yours as possible approach.
* unexpected()/error() should have a narrow contract (but this needs to be discussed in Toronto)
Please no.
:)
I don't see how get<I> can work here.
https://rawgit.com/pdimov/mp11/develop/doc/html/mp11.html#mp11.reference.alg...
I don't see it yet. What is the type of I. How can you use a function parameter as a template argument? return mp_with_index<mp_size<expected>>( v_.index(), [&]( auto I // fct parameter ) { return this->_remap_error<R>( I, f, get< I // templ argument
(v_) );
* ::then seems to be the monad::bind, could you confirm?
It's a monadic bind, yes. I'll remove .then in favor of operator>>, as documented.
* Wondering from constructors from convertibles expected<T,E...> from expected<U, G...>.
I'm not sure yet whether to enable value conversion. Pretty sure we don't want error conversions outside remap_errors.
* Factories are missing
Since we have argument deduction now, expected{ expr } and unexpected_{ expr } can be used for the purpose, and I saw no need for separate factory functions.
For the standard this *could* be good, but will your library be C++17 only? Vicente

Vicente J. Botet Escriba wrote:
expected<T> is valid. Its purpose is to make expected{ expr } valid (the equivalent of make_expected.)
But make_expected() returns expected<T, error_code> I understand that when designing expected<T, E...> we need to reconsider some functions.
BTW, to what it is useful to have a expected<T> without a possible error? is expected<T> convertible to expected<T,E...>?
expected<T, E1...> is convertible to expected<T, E2...>, where all error types in `E1...` also occur in `E2...`. So it follows that expected<T> is convertible to expected<T, E...> for any `E...`. That is, you can `return expected{ x }` in a function returning expected<X, E...>.
I don't see how expected<T, E...> could default to expected<T, error_code>?
It doesn't default to anything, no.
Does it mean that we need an additional template alias
template <class T, class R = error_code> using expected2<T, E>;
We could call this alias... `result<T>`! :-)
expected<variant<int,string>, int, string> ? unexpected_type<E> was used for that to avoid ambiguity.
Yes, I suppose you could do that. I agree that unexpected_<E...> should be a distinct type. For now I've taken a shortcut, but this will be fixed.
I don't see how get<I> can work here.
https://rawgit.com/pdimov/mp11/develop/doc/html/mp11.html#mp11.reference.alg...
I don't see it yet. What is the type of I. How can you use a function parameter as a template argument?
The type of I is mp_size_t<I'>, an alias for std::integral_constant<std::size_t, I'>. Since integral_constant has a constexpr conversion to its value, it can be used directly as if it were the compile-time value (modulo compiler bugs.) So get<I>, get<I.value> and get<decltype(I)::value> all amount to the same thing. C++14 is magic.
Since we have argument deduction now, expected{ expr } and unexpected_{ expr } can be used for the purpose, and I saw no need for separate factory functions.
For the standard this *could* be good, but will your library be C++17 only?
For a library that targets C++20, it does seem reasonable for the proposed interface to be optimized for C++17. If this becomes a Boost library, I could include make_expected and make_unexpected to ease C++14 use, but I don't think we need them in the formal proposal.

Le 04/06/2017 à 17:55, Peter Dimov via Boost a écrit :
Vicente J. Botet Escriba wrote:
expected<T> is valid. Its purpose is to make expected{ expr } valid (the > equivalent of make_expected.)
But make_expected() returns expected<T, error_code> I understand that when designing expected<T, E...> we need to reconsider some functions.
BTW, to what it is useful to have a expected<T> without a possible error? is expected<T> convertible to expected<T,E...>?
expected<T, E1...> is convertible to expected<T, E2...>, where all error types in `E1...` also occur in `E2...`. So it follows that expected<T> is convertible to expected<T, E...> for any `E...`.
That is, you can `return expected{ x }` in a function returning expected<X, E...>. Great.
I don't see how expected<T, E...> could default to expected<T, error_code>?
It doesn't default to anything, no.
Does it mean that we need an additional template alias
template <class T, class R = error_code> using expected2<T, E>;
We could call this alias... `result<T>`! :-) Sure, and what about alias and factories.
expected<variant<int,string>, int, string> ? unexpected_type<E> was used for that to avoid ambiguity.
Yes, I suppose you could do that. I agree that unexpected_<E...> should be a distinct type. For now I've taken a shortcut, but this will be fixed. Glad to see we agree.
I don't see how get<I> can work here.
https://rawgit.com/pdimov/mp11/develop/doc/html/mp11.html#mp11.reference.alg...
I don't see it yet. What is the type of I. How can you use a function parameter as a template argument?
The type of I is mp_size_t<I'>, an alias for std::integral_constant<std::size_t, I'>. Since integral_constant has a constexpr conversion to its value, it can be used directly as if it were the compile-time value (modulo compiler bugs.) So get<I>, get<I.value> and get<decltype(I)::value> all amount to the same thing. C++14 is magic. Wow, I didn't was aware of this possibility.
Since we have argument deduction now, expected{ expr } and > unexpected_{ expr } can be used for the purpose, and I saw no need for > separate factory functions.
For the standard this *could* be good, but will your library be C++17 only?
For a library that targets C++20, it does seem reasonable for the proposed interface to be optimized for C++17. If this becomes a Boost library, I could include make_expected and make_unexpected to ease C++14 use, but I don't think we need them in the formal proposal. Agreed. the standard and Boost needs two different libraries.
What do you think of making expected constructor from T explicit? What we lost by making it explicit? Implicit use expected<double, unscoped_error> test() { return unscoped_other_error; // returns 7.0 } Explicit use expected<double, unscoped_error> test() { return expected{unscoped_other_error}; // returns value 7.0 // make_expected } expected<double, unscoped_error> test() { return unexpected_{unscoped_other_error}; // returns Error 7 // make_unexpected } Vicente

Vicente J. Botet Escriba wrote:
What do you think of making expected constructor from T explicit?
That's one of the options. Another is to make
expected<double, unscoped_error> test() { return unscoped_other_error; }
work and return an error, instead of a double (exact match to E). I know that there are people who insist on explicit everywhere, but implicit does have its appeal. Of course in pathological examples such as expected<variant<int, float>, int, float>, implicit conversions will rarely do what one wants. But I'd argue that these (IMO atypical) uses should not carry much weight. expected<double, int> is a more likely case, and there's no way to make implicit conversions always do the right thing for it. I'm not sure how much we ought to focus on it. Perhaps the heuristic could be, if the argument can be interpreted as both a value and an error, the conversion is ambiguous, regardless of which of the two is a better match. This will make the above return ambiguous, which is probably as it should be.

On Sun, Jun 4, 2017 at 12:35 PM, Peter Dimov via Boost <boost@lists.boost.org> wrote:
Vicente J. Botet Escriba wrote:
What do you think of making expected constructor from T explicit?
That's one of the options. Another is to make
expected<double, unscoped_error> test() { return unscoped_other_error; }
work and return an error, instead of a double (exact match to E).
I know that there are people who insist on explicit everywhere, but implicit does have its appeal.
Of course in pathological examples such as expected<variant<int, float>, int, float>, implicit conversions will rarely do what one wants. But I'd argue that these (IMO atypical) uses should not carry much weight.
expected<double, int> is a more likely case, and there's no way to make implicit conversions always do the right thing for it. I'm not sure how much we ought to focus on it.
Perhaps the heuristic could be, if the argument can be interpreted as both a value and an error, the conversion is ambiguous, regardless of which of the two is a better match. This will make the above return ambiguous, which is probably as it should be.
I think I might agree with that. There will definitely be cases where expected<T,E> has T == int and E == int, and worse, sometimes those cases will be: - T is a template param, so you don't "see" that it is sometimes an int - E is a #define or platform-defined, etc, so it is only _sometimes_ an int Tony

Gottlob Frege wrote:
On Sun, Jun 4, 2017 at 12:35 PM, Peter Dimov via Boost
Perhaps the heuristic could be, if the argument can be interpreted as both a value and an error, the conversion is ambiguous, regardless of which of the two is a better match. This will make the above return ambiguous, which is probably as it should be.
I think I might agree with that. There will definitely be cases where expected<T,E> has T == int and E == int, and worse, sometimes those cases will be: - T is a template param, so you don't "see" that it is sometimes an int - E is a #define or platform-defined, etc, so it is only _sometimes_ an int
T == int, E == int is not a problem, because it will always be ambiguous. The thorny case is E == int, T == something convertible from int, such as double, long, unsigned.

Le 04/06/2017 à 22:40, Peter Dimov via Boost a écrit :
Gottlob Frege wrote:
Perhaps the heuristic could be, if the argument can be interpreted as > both a value and an error, the conversion is ambiguous, regardless of > which of the two is a better match. This will make
On Sun, Jun 4, 2017 at 12:35 PM, Peter Dimov via Boost the above return > ambiguous, which is probably as it should be.
I think I might agree with that. There will definitely be cases where expected<T,E> has T == int and E == int, and worse, sometimes those cases will be: - T is a template param, so you don't "see" that it is sometimes an int - E is a #define or platform-defined, etc, so it is only _sometimes_ an int
T == int, E == int is not a problem, because it will always be ambiguous. The thorny case is E == int, T == something convertible from int, such as double, long, unsigned.
Lets imagine ofr a moment that we allowed implicit conversion from convertible to T and convertible to E as far as both constructions are not possible. expected<double, unscoped_error> test() { return unscoped_other_error; // Compile Error as ambiguous } expected<double, unscoped_error> test() { return make_unexpected(unscoped_other_error); } Then if we want to be more explicit, then we can use wrappers that force us to be explicit: expected_ and unexpected_ are wrappers that are explicitly constructible from the wrapped type and provide explicit access to the value. expected<double, unexpected_<unscoped_error>> test() { return unexpected_{unscoped_other_error}; } Note that unexpect_<E> can not be implicltly convertible to E as otherwise we will have again an ambiguity. If we want also to be explicit on T expected<expected_<double>, unexpected_<unscoped_error>> test() { return expected_{3.0}; } The problem now is that we need to unwrap the wrapped types as the conversion is not implicit. My original design with an implicit unexpected_type was to avoid this unwrap operation, and define getter for unexpected_type<E> and also for the wrapped E. Given an expected that has the implicit constructors, we can build another expected_expl that has his constructors explicit and provide unwrapping operation. The same can be achieve from a expected that has explicit constructors, we can define a expected_impl that has his constructors implicit. Do we need both approaches? I don't know.. Vicente

Vicente J. Botet Escriba wrote:
* remap_errors seems close to what I've called adapt
`adapt` is a good name. I may adopt it. (Or adapt to it.) There is one open question. With the given interface, with conversions from T implicit and conversions from an error explicit, consider what happens here: enum unscoped_error { unscoped_other_error = 7 }; expected<double, unscoped_error> test() { return unscoped_other_error; // returns 7.0 } This seems undesirable. To prevent it, we either need to have both explicit, or both implicit. I'm tending toward the latter (but if an error type is chosen by the variant-like overload resolution, I plan to static_assert on exact match.)

Le 04/06/2017 à 14:40, Peter Dimov via Boost a écrit :
Vicente J. Botet Escriba wrote:
* remap_errors seems close to what I've called adapt
`adapt` is a good name. I may adopt it. (Or adapt to it.)
There is one open question. With the given interface, with conversions from T implicit and conversions from an error explicit, consider what happens here:
enum unscoped_error { unscoped_other_error = 7 };
expected<double, unscoped_error> test() { return unscoped_other_error; // returns 7.0 }
This seems undesirable. To prevent it, we either need to have both explicit, or both implicit. I'm tending toward the latter (but if an error type is chosen by the variant-like overload resolution, I plan to static_assert on exact match.)
unexpected_type<E> is explicitly constructible from E. expected<T, E> is implicitly constructible from unexpected_type<E>. This avoid this kind of ambiguities. With a variadic expected<T, E...> we need that it to be constructible from unexpected_type<Err> for each Err in E... This would mean that unexpected_type<E...> or what you name unexpected_<E...> can not be an alias of variant<E...>. Wondering if your example doesn't justify an explicit constructor from T as well. This is how I see it. We can not agree. Vicente

Vicente J. Botet Escriba wrote:
enum unscoped_error { unscoped_other_error = 7 };
expected<double, unscoped_error> test() { return unscoped_other_error; // returns 7.0 }
...
unexpected_type<E> is explicitly constructible from E. expected<T, E> is implicitly constructible from unexpected_type<E>. This avoid this kind of ambiguities.
Could you please try this code with your implementation?

Le 04/06/2017 à 17:42, Peter Dimov via Boost a écrit :
Vicente J. Botet Escriba wrote:
enum unscoped_error { unscoped_other_error = 7 };
expected<double, unscoped_error> test() { return unscoped_other_error; // returns 7.0 }
...
unexpected_type<E> is explicitly constructible from E. expected<T, E> is implicitly constructible from unexpected_type<E>. This avoid this kind of ambiguities.
Could you please try this code with your implementation? It will work as yours. My concern is that this is not what we want, isn't it?
Vicente

Vicente J. Botet Escriba wrote:
Le 04/06/2017 à 17:42, Peter Dimov via Boost a écrit :
Vicente J. Botet Escriba wrote:
enum unscoped_error { unscoped_other_error = 7 };
expected<double, unscoped_error> test() { return unscoped_other_error; // returns 7.0 }
...
unexpected_type<E> is explicitly constructible from E. expected<T, E> is implicitly constructible from unexpected_type<E>. This avoid this kind of ambiguities.
Could you please try this code with your implementation?
It will work as yours. My concern is that this is not what we want, isn't it?
It's not, but what you say above - "this avoid this kind of ambiguities" - created the impression that you think that it isn't a problem. As I see it, this can be avoided by either making the constructor from T explicit as well, or by making the constructor from E implicit as well.

Le 04/06/2017 à 18:05, Peter Dimov via Boost a écrit :
Vicente J. Botet Escriba wrote:
Vicente J. Botet Escriba wrote:
enum unscoped_error { unscoped_other_error = 7 };
expected<double, unscoped_error> test() { return unscoped_other_error; // returns 7.0 }
...
unexpected_type<E> is explicitly constructible from E. expected<T, E> >> is implicitly constructible from unexpected_type<E>. This avoid
Le 04/06/2017 à 17:42, Peter Dimov via Boost a écrit : this >> kind of ambiguities.
Could you please try this code with your implementation?
It will work as yours. My concern is that this is not what we want, isn't it?
It's not, but what you say above - "this avoid this kind of ambiguities" - created the impression that you think that it isn't a problem. It could be a problem for some. I'm one of the ones this is a problem. Thanks for pointing this case.
As I see it, this can be avoided by either making the constructor from T explicit as well, or by making the constructor from E implicit as well. I'm all for explicit constructors, from T and from E via unexpected_type<E>.
Vicente

Niall I'm trying to see what you want to propose by difference to a hypothetical generic interface to see what is the added value. Le 27/05/2017 à 16:13, Niall Douglas via Boost a écrit :
- outcome<T> and result<T> will have their empty state removed, and all observers gain narrow contracts. Default construction is disabled. https://github.com/ned14/boost.outcome/issues/44. Example:
``` result<Foo> v(make_errored_result(std::errc::invalid_argument)); assert(v.has_error()); // true assert(!v.has_value()); // true // As if Foo(reinterpret_cast<Foo &&> // (error_code_extended(std::errc::invalid_argument)); Foo f(std::move(v.value())); assert(v.has_error()); // still true assert(!v.has_value()); // still true ``` Just a question I could do for the existing library also. What has_error mean for outcome, result? is that it has an EC or that it has no value? .has_error() is true if and only if the current state is an error_code_extended instance. Okay. Why don't you name it has_error_code? For me having an exception_ptr stored was also an error.
Why not name the getter error function get_error_code, .... as the function doesn't returns a generic Error but one of them. I don't think using the same name for different things is helping in generic programming.
And now that we don't have empty, what is the sens of has_error for result?
There will be no longer an empty state in result<T>, but for generic programming I'll be retaining a .has_error() so end users can more easily swap a result<T> for a result_e<T>.
:( I see generic programming in other terms.
Maybe, outcome should have a get_state function that returns an enum so that the user can do a switch. I had previously a .visit(callable) for that.
Any sum type should provide a visit function. I've implemented it in the std_make repository. I will add it to Expected proposal once we have a SumType type of classes.
(NOTE: expected<T, E> will track whatever LEWG Expected does, but it differs from what will become result<T> by having a wide contract on .value() and narrow contracts on operator*(), .error(), operator->(). result<T> will have narrow contracts on everything, it is basically a thin wrap of std::variant<T, error_code_extended> except with strong never empty warranty) What will be the differences between result<T> and expected<T, error_code_extended>?
The wide contracts for the observers? Can not we provide wide and narrow contracts or don't reuse the same name with different meaning? The description above quite literally tells you the differences.
Why we cannot provide wide and narrow observers? If this is the single difference I don't see the need for having two types.
If we had a expected<T, E1, .., En> what will be the differences between outcome<T> and expected<T, error_code_extended, exception_ptr>? I would assume an expected<T, E1, .., En> could only return a std::variant<E1, ..., En> from its .error(). I can't think what else it could do.
outcome<T> continues to provide: - T& .value() - error_code_extended .error() - std::exception_ptr .exception()
i.e. hard coded. Ok, so the single difference would be about the error observers returning by value and having an explicit name.
- New typedefs outcome_e<T> and result_e<T> are identical to outcome<T> and result<T> except for adding a formal empty state. Observer contract slightly widens, an attempt to use an empty object throws a bad_outcome_access exception. Implicit conversion from non-empty-capable varieties is permitted to empty-capable varieties, but not the other way round. Default construction is to **empty**. https://github.com/ned14/boost.outcome/issues/44 Okay this corresponds to what others are naming optional_outcome, optional_result. I'm not wedded to result_e<T> etc. Ok, I'll change result_e<T> and outcome_e<T> to optional_result<T> and optional_outcome<T>. Done at https://github.com/ned14/boost.outcome/issues/44 Neither me. Just wanted to name the types in a more explicit way. result_e doesn't tells me the intent. If we had a optional_expected<T, E1, .., En> what will be the differences between result_e<T> and optional_expected<T, error_code_extended>? what will be the differences between outcome_e<T> and optional_expected<T, error_code_extended, exception_ptr>? I am not sure what semantics you have chosen for optional_expected<>.
This function *could* return variant<E1, ..., En> or variant<E1&, ..., En&> or any sum type that represents the error, that is the Not-A-Value. Maybe, expected<T, E1, ...>::get_error<Ek> could be more useful. In any case expected<T, E1, ..., En> should provide a visit() function. Having more than one Error makes this absolutely necessary. When having more than one error in expected, having access to each one of them using a different interface would make the user code more complex, isn't it? optional_expected<T, ... > should be an optimize version of optional<expected<T, ...>>.
optional_outcome<T> is exactly like outcome<T> but with an added empty state and a default constructor which constructs to empty. Otherwise identical.
- New typedefs checked_outcome<T>/checked_result<T>, checked_outcome_e<T>/checked_result_e<T> are added. These mirror the editions just described, but checks and default actions occur on all observer usage so hidden reinterpret_cast<> never occurs. Implicit conversion from non-checked varieties is permitted to checked varieties, but not the other way round. https://github.com/ned14/boost.outcome/issues/47. Examples:
``` // Note result<T> implicitly converts to checked_result<T>, but not // the other way round. So we can use same make_errored_result(). checked_result<Foo> v(make_errored_result(std::errc::invalid_argument)); assert(v.has_error()); // true assert(!v.has_value()); // true // .value() throws std::system_error( // std::make_error_code(std::errc::invalid_argument)); Foo f(std::move(v.value())); ```
``` checked_result<Foo> v(make_valued_result(Foo())); assert(!v.has_error()); // true assert(v.has_value()); // true // .error() returns a default constructed (null) error_code_extended // when result is valued to indicate "no error here" error_code_extended ec(std::move(v.error())); assert(!ec); // true ``` I will need more rationale about the need of this classes and why we need to do an action while observing them. Please, could you elaborate? checked_optional_outcome<T> equals exactly the outcome<T> in the presented library.
checked_optional_result<T> equals exactly the result<T> in the presented library.
All the discussion of the presented library to date applies to the checked_*() functions.
if error returns the stored error or a default error when valued, why we don't use another name for the function? Why do we want to change the signature and the semantic of an existing function (when I say existing, I'm referring so std::experimental::expected, or for ). Why not have always the same interface and adding an error_or function as suggested in the Expected proposal? This function could be generic as far as we have common interface. What your checked_outcome<T>::error will return if the is a exception_ptr?
Still to be decided: ==================== - Should *_e() varieties provide convenience .get(), .get_error(), .get_exception() which returns T, error_code_extended and std::exception_ptr by value moved from internal state, resetting state afterwards to empty? These would mirror future.get()'s single shot observers. We need a valid use case to introduce them and even more as member functions. In any case, these functions can be defined on to of the provided interface, and could be non-member functions, isn't it? There is an argument to make all the observers of Expected or Outcome free functions. It certainly would fit how std::begin() etc work. So:
expected<T, E> foo(T());
if(has_value(foo)) ...
T x(std::move(value(foo)));
T y(std::move(get(foo)));
... and so on.
I see a difference between the minimal interface and other functions. You can not define has_value in function of other functions. You could define a wide value() function using has_value() and deref()/operartor*. If you go the future::get way, don't forget that you cannot set twice a promise. Vicente

2017-05-28 11:35 GMT+02:00 Vicente J. Botet Escriba via Boost < boost@lists.boost.org>:
Niall I'm trying to see what you want to propose by difference to a hypothetical generic interface to see what is the added value.
Le 27/05/2017 à 16:13, Niall Douglas via Boost a écrit :
- outcome<T> and result<T> will have their empty state removed, and all
observers gain narrow contracts. Default construction is disabled. https://github.com/ned14/boost.outcome/issues/44. Example:
``` result<Foo> v(make_errored_result(std::errc::invalid_argument)); assert(v.has_error()); // true assert(!v.has_value()); // true // As if Foo(reinterpret_cast<Foo &&> // (error_code_extended(std::errc::invalid_argument)); Foo f(std::move(v.value())); assert(v.has_error()); // still true assert(!v.has_value()); // still true ```
Just a question I could do for the existing library also. What has_error mean for outcome, result? is that it has an EC or that it has no value?
.has_error() is true if and only if the current state is an error_code_extended instance.
Okay. Why don't you name it has_error_code? For me having an exception_ptr stored was also an error.
Why not name the getter error function get_error_code, .... as the function doesn't returns a generic Error but one of them.
I don't think using the same name for different things is helping in generic programming.
And now that we don't have empty, what is the sens of has_error for
result?
There will be no longer an empty state in result<T>, but for generic programming I'll be retaining a .has_error() so end users can more easily swap a result<T> for a result_e<T>.
:(
I see generic programming in other terms.
Maybe, outcome should have a get_state function that returns an enum so
that the user can do a switch.
I had previously a .visit(callable) for that.
Any sum type should provide a visit function. I've implemented it in the std_make repository. I will add it to Expected proposal once we have a SumType type of classes.
(NOTE: expected<T, E> will track whatever LEWG Expected does, but it
differs from what will become result<T> by having a wide contract on .value() and narrow contracts on operator*(), .error(), operator->(). result<T> will have narrow contracts on everything, it is basically a thin wrap of std::variant<T, error_code_extended> except with strong never empty warranty)
What will be the differences between result<T> and expected<T, error_code_extended>?
The wide contracts for the observers? Can not we provide wide and narrow contracts or don't reuse the same name with different meaning?
The description above quite literally tells you the differences.
Why we cannot provide wide and narrow observers? If this is the single difference I don't see the need for having two types.
If we had a expected<T, E1, .., En> what will be the differences between
outcome<T> and expected<T, error_code_extended, exception_ptr>?
I would assume an expected<T, E1, .., En> could only return a std::variant<E1, ..., En> from its .error(). I can't think what else it could do.
This function *could* return variant<E1, ..., En> or variant<E1&, ..., En&> or any sum type that represents the error, that is the Not-A-Value.
Maybe, expected<T, E1, ...>::get_error<Ek> could be more useful.
In any case expected<T, E1, ..., En> should provide a visit() function. Having more than one Error makes this absolutely necessary.
When having more than one error in expected, having access to each one of them using a different interface would make the user code more complex, isn't it?
outcome<T> continues to provide:
- T& .value() - error_code_extended .error() - std::exception_ptr .exception()
i.e. hard coded.
Ok, so the single difference would be about the error observers returning by value and having an explicit name.
- New typedefs outcome_e<T> and result_e<T> are identical to outcome<T>
and result<T> except for adding a formal empty state. Observer contract slightly widens, an attempt to use an empty object throws a bad_outcome_access exception. Implicit conversion from non-empty-capable varieties is permitted to empty-capable varieties, but not the other way round. Default construction is to **empty**. https://github.com/ned14/boost.outcome/issues/44
Okay this corresponds to what others are naming optional_outcome, optional_result.
I'm not wedded to result_e<T> etc. Ok, I'll change result_e<T> and outcome_e<T> to optional_result<T> and optional_outcome<T>. Done at https://github.com/ned14/boost.outcome/issues/44
Neither me. Just wanted to name the types in a more explicit way. result_e doesn't tells me the intent.
If we had a optional_expected<T, E1, .., En>
what will be the differences between result_e<T> and optional_expected<T, error_code_extended>? what will be the differences between outcome_e<T> and optional_expected<T, error_code_extended, exception_ptr>?
I am not sure what semantics you have chosen for optional_expected<>.
optional_expected<T, ... > should be an optimize version of optional<expected<T, ...>>.
According to Niall's example, it was my understanding that his mental model was `expected<optional<T>, ...>`. That is, empty resutl means: no error ocurred, I successfully computed the non-value. Regards, &rzej;

I am not sure what semantics you have chosen for optional_expected<>.
optional_expected<T, ... > should be an optimize version of optional<expected<T, ...>>.
According to Niall's example, it was my understanding that his mental model was `expected<optional<T>, ...>`. That is, empty resutl means: no error ocurred, I successfully computed the non-value.
That code example I posted I deliberately overrode the default handling of the empty state to mean no error occurred. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Niall I'm trying to see what you want to propose by difference to a hypothetical generic interface to see what is the added value.
Sure. We are trying to achieve different things though. Your Expected needs to be a superb primitive building block useful for making other stuff. It's why I would be much happier with narrow contracts on your observers, it's easy for someone to derive from expected<T, E> locally and override narrow observers with wide observers, or add new member functions with wide contracts. The harder question for you is whether to supply that wide observer alternative yourself. Do you supply a completely different name of type to help public APIs distinguish which contract is being supplied? Or do you provide a second, wide set of observer member functions? These are hard choices.
.has_error() is true if and only if the current state is an error_code_extended instance. Okay. Why don't you name it has_error_code? For me having an exception_ptr stored was also an error.
My thinking was that an exception isn't an error. It's *exceptional*. An error is just well, an error. It is not as strong.
Maybe, outcome should have a get_state function that returns an enum so that the user can do a switch. I had previously a .visit(callable) for that. Any sum type should provide a visit function. I've implemented it in the std_make repository. I will add it to Expected proposal once we have a SumType type of classes.
My sole reason for leaving it out of the presented library (even though it's actually in the source code, just disabled) was compile time impact. You can easily make do without it.
(NOTE: expected<T, E> will track whatever LEWG Expected does, but it differs from what will become result<T> by having a wide contract on .value() and narrow contracts on operator*(), .error(), operator->(). result<T> will have narrow contracts on everything, it is basically a thin wrap of std::variant<T, error_code_extended> except with strong never empty warranty) What will be the differences between result<T> and expected<T, error_code_extended>?
The wide contracts for the observers? Can not we provide wide and narrow contracts or don't reuse the same name with different meaning? The description above quite literally tells you the differences.
Why we cannot provide wide and narrow observers? If this is the single difference I don't see the need for having two types.
The whole point behind Outcome's wide observers with default actions is to save typing boilerplate. Typing more code to get more safety seems the wrong way round to me. It should always be the incentive that to get *less* safety the programmer must explicitly type more code. Now if you agree with that, would the following change to Expected be acceptable: - value_type& .value() - throws bad_expected_access<error_type> if not valued - error_type .error() - returns default constructed error_type if not errored - value_type& .value_raw() - reinterpret_cast<value_type&> - error_type& .error_raw() - reinterpret_cast<error_type&> If you feel you can reach this, or anything where less safety forces the programmer to type more, I can match that in Outcome.
I would assume an expected<T, E1, .., En> could only return a std::variant<E1, ..., En> from its .error(). I can't think what else it could do. This function *could* return variant<E1, ..., En> or variant<E1&, ..., En&> or any sum type that represents the error, that is the Not-A-Value.
Maybe, expected<T, E1, ...>::get_error<Ek> could be more useful.
Probably yes.
In any case expected<T, E1, ..., En> should provide a visit() function. Having more than one Error makes this absolutely necessary.
When having more than one error in expected, having access to each one of them using a different interface would make the user code more complex, isn't it?
Yes if all kinds of failure are treated as an error. Outcome distinctly categorises failure-due-to-error and failure-due-to-exception. I think that important given its different use case to Expected which is explicitly for low latency and C++ disabled environments. But Expected as the STL type, I think your proposal is safe. Most of your users will throw exceptions for exceptional situations.
- New typedefs checked_outcome<T>/checked_result<T>, checked_outcome_e<T>/checked_result_e<T> are added. These mirror the editions just described, but checks and default actions occur on all observer usage so hidden reinterpret_cast<> never occurs. Implicit conversion from non-checked varieties is permitted to checked varieties, but not the other way round. https://github.com/ned14/boost.outcome/issues/47. Examples:
``` // Note result<T> implicitly converts to checked_result<T>, but not // the other way round. So we can use same make_errored_result(). checked_result<Foo> v(make_errored_result(std::errc::invalid_argument)); assert(v.has_error()); // true assert(!v.has_value()); // true // .value() throws std::system_error( // std::make_error_code(std::errc::invalid_argument)); Foo f(std::move(v.value())); ```
``` checked_result<Foo> v(make_valued_result(Foo())); assert(!v.has_error()); // true assert(v.has_value()); // true // .error() returns a default constructed (null) error_code_extended // when result is valued to indicate "no error here" error_code_extended ec(std::move(v.error())); assert(!ec); // true ``` I will need more rationale about the need of this classes and why we need to do an action while observing them. Please, could you elaborate?
To save writing boilerplate in the most common use cases.
if error returns the stored error or a default error when valued, why we don't use another name for the function?
You mean other than .error()?
Why do we want to change the signature and the semantic of an existing function (when I say existing, I'm referring so std::experimental::expected, or for ).
Why not have always the same interface and adding an error_or function as suggested in the Expected proposal? This function could be generic as far as we have common interface.
Again, less boilerplate. Expected, because it lets users choose their E type, unavoidably requires more boilerplate. I don't like typing boilerplate, it makes my fingers and wrist hurt more, it clutters my source code, hence Outcome.
What your checked_outcome<T>::error will return if the is a exception_ptr?
Ah, finally someone brought this up. Good! It returns an error_code with bad_outcome_category and bad_outcome_errc::exception_present. This is despite .has_error() being false! Now I think that this is the right way of implementing this, but I'll straight away agree that it is not uncontroversial to me. My argument would be that it is a magic cookie error code to indicate you've done a logic error. Some would say, can't you throw an exception, it is a logic error after all? That would be my second preference if this magic cookie is felt by reviewers to be unwise. I definitely do not think .has_error() should be true if the outcome is exceptioned. And yes, I do get the irony here considering my arguments about not removing the empty state. I will tell you one thing: in all my nearly two years of using Outcome, I have never yet seen a bad_outcome_errc::exception_present error_code turn up. It has made it hard for me to be sure if this choice is the right call or not. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Le 29/05/2017 à 00:39, Niall Douglas via Boost a écrit :
Niall I'm trying to see what you want to propose by difference to a hypothetical generic interface to see what is the added value. Sure. We are trying to achieve different things though. Your Expected needs to be a superb primitive building block useful for making other stuff. It's why I would be much happier with narrow contracts on your observers, it's easy for someone to derive from expected<T, E> locally and override narrow observers with wide observers, or add new member functions with wide contracts.
The harder question for you is whether to supply that wide observer alternative yourself. Do you supply a completely different name of type to help public APIs distinguish which contract is being supplied? Or do you provide a second, wide set of observer member functions?
These are hard choices. I will have no problem providing a wide_error() and wide_get_unexpected functions. I believe these are the only missing in expected. We need just to find a good name.
.has_error() is true if and only if the current state is an error_code_extended instance. Okay. Why don't you name it has_error_code? For me having an exception_ptr stored was also an error. My thinking was that an exception isn't an error. It's *exceptional*. An error is just well, an error. It is not as strong. When I mean an error is not an error_code but any reason the computation failed. Do you have a good name for that? failure()?
Maybe, outcome should have a get_state function that returns an enum so that the user can do a switch. I had previously a .visit(callable) for that. Any sum type should provide a visit function. I've implemented it in the std_make repository. I will add it to Expected proposal once we have a SumType type of classes. My sole reason for leaving it out of the presented library (even though it's actually in the source code, just disabled) was compile time impact. You can easily make do without it. Up to you.
(NOTE: expected<T, E> will track whatever LEWG Expected does, but it differs from what will become result<T> by having a wide contract on .value() and narrow contracts on operator*(), .error(), operator->(). result<T> will have narrow contracts on everything, it is basically a thin wrap of std::variant<T, error_code_extended> except with strong never empty warranty) What will be the differences between result<T> and expected<T, error_code_extended>?
The wide contracts for the observers? Can not we provide wide and narrow contracts or don't reuse the same name with different meaning? The description above quite literally tells you the differences. Why we cannot provide wide and narrow observers? If this is the single difference I don't see the need for having two types. The whole point behind Outcome's wide observers with default actions is to save typing boilerplate. Typing more code to get more safety seems the wrong way round to me. It should always be the incentive that to get *less* safety the programmer must explicitly type more code.
Now if you agree with that, would the following change to Expected be acceptable:
- value_type& .value() - throws bad_expected_access<error_type> if not valued
- error_type .error() - returns default constructed error_type if not errored Not for me, but maybe the committee agree with. I will name this error_or. - value_type& .value_raw() - reinterpret_cast<value_type&> We have already operator*. in expected Your library can add it.
- error_type& .error_raw() - reinterpret_cast<error_type&> evidently we don't like the _raw suffix.
If you feel you can reach this, or anything where less safety forces the programmer to type more, I can match that in Outcome. I'm not alone in my case. It will be a consensus of the committee.
I would assume an expected<T, E1, .., En> could only return a std::variant<E1, ..., En> from its .error(). I can't think what else it could do. This function *could* return variant<E1, ..., En> or variant<E1&, ..., En&> or any sum type that represents the error, that is the Not-A-Value.
Maybe, expected<T, E1, ...>::get_error<Ek> could be more useful. Probably yes.
In any case expected<T, E1, ..., En> should provide a visit() function. Having more than one Error makes this absolutely necessary.
When having more than one error in expected, having access to each one of them using a different interface would make the user code more complex, isn't it? Yes if all kinds of failure are treated as an error.
Outcome distinctly categorises failure-due-to-error and failure-due-to-exception. I think that important given its different use case to Expected which is explicitly for low latency and C++ disabled environments. Do you mean exceptions disabled? How would you have exceptions when the exceptions are disabled?
But Expected as the STL type, I think your proposal is safe. Most of your users will throw exceptions for exceptional situations. I guess that when exceptions are disabled, an implementation will just terminate the program, isn't it?.
- New typedefs checked_outcome<T>/checked_result<T>, checked_outcome_e<T>/checked_result_e<T> are added. These mirror the editions just described, but checks and default actions occur on all observer usage so hidden reinterpret_cast<> never occurs. Implicit conversion from non-checked varieties is permitted to checked varieties, but not the other way round. https://github.com/ned14/boost.outcome/issues/47. Examples:
``` // Note result<T> implicitly converts to checked_result<T>, but not // the other way round. So we can use same make_errored_result(). checked_result<Foo> v(make_errored_result(std::errc::invalid_argument)); assert(v.has_error()); // true assert(!v.has_value()); // true // .value() throws std::system_error( // std::make_error_code(std::errc::invalid_argument)); Foo f(std::move(v.value())); ```
``` checked_result<Foo> v(make_valued_result(Foo())); assert(!v.has_error()); // true assert(v.has_value()); // true // .error() returns a default constructed (null) error_code_extended // when result is valued to indicate "no error here" error_code_extended ec(std::move(v.error())); assert(!ec); // true ``` I will need more rationale about the need of this classes and why we need to do an action while observing them. Please, could you elaborate? To save writing boilerplate in the most common use cases.
if error returns the stored error or a default error when valued, why we don't use another name for the function? You mean other than .error()? yes.
Why do we want to change the signature and the semantic of an existing function (when I say existing, I'm referring so std::experimental::expected, or for ).
Why not have always the same interface and adding an error_or function as suggested in the Expected proposal? This function could be generic as far as we have common interface. Again, less boilerplate.
Expected, because it lets users choose their E type, unavoidably requires more boilerplate. I don't like typing boilerplate, it makes my fingers and wrist hurt more, it clutters my source code, hence Outcome. I prefer to have a uniform interface even if this mean typing more.
I can not ignore this Outcome review, but I have not see too much people saying that the semantic you have done to this functions is the good one. Nevertheless I will add an open point about having narrow and wide contract for the error access. I will add also another open point to have the possibility to return E{} when the contract is wide instead of throwing an exception. IMHO we need just the narrow contract function. The other can be built on top of this by the user or by Outcome. Remember Expected proposal has already two open_points for err error_or(exp, err) and bool has_error(exp, err)
What your checked_outcome<T>::error will return if the is a exception_ptr? Ah, finally someone brought this up. Good!
It returns an error_code with bad_outcome_category and bad_outcome_errc::exception_present.
Ugh. Why your design is never coherent. Why you don't name your functions after what they do? What if the exception_ptr contains an arbitrary exception? No way to obtain an error_code :( I believe your design is too much guided by your AFIO use case. Not all the applications use system errors.
This is despite .has_error() being false! Now I think that this is the right way of implementing this, but I'll straight away agree that it is not uncontroversial to me. My argument would be that it is a magic cookie error code to indicate you've done a logic error. Some would say, can't you throw an exception, it is a logic error after all? That would be my second preference if this magic cookie is felt by reviewers to be unwise. I definitely do not think .has_error() should be true if the outcome is exceptioned. And yes, I do get the irony here considering my arguments about not removing the empty state.
I will tell you one thing: in all my nearly two years of using Outcome, I have never yet seen a bad_outcome_errc::exception_present error_code turn up. It has made it hard for me to be sure if this choice is the right call or not.
Whether you have seen them or not it is out of scope. Your program needs to manage them. Vicente

On 29/05/2017 18:12, Vicente J. Botet Escriba wrote:
What your checked_outcome<T>::error will return if the is a exception_ptr? Ah, finally someone brought this up. Good!
It returns an error_code with bad_outcome_category and bad_outcome_errc::exception_present. Ugh. Why your design is never coherent. Why you don't name your functions after what they do? What if the exception_ptr contains an arbitrary exception? No way to obtain an error_code :( I believe your design is too much guided by your AFIO use case. Not all the applications use system errors.
I think you may have misinterpreted that. Niall's saying (if I haven't misinterpreted myself) that calling error() on an outcome that contains any arbitrary exception will give that one specific error code (which basically tells you that your code is probably wrong to have asked, but it's not a precondition violation). That sounds like it's what you're wanting, and it's probably the behaviour I'd expect. (There might be some argument that if the contained exception happens to be a std::system_error then it might be nice if the contained error_code could be extracted and returned, but that's not what Niall said and it sounds like that's what you're objecting to.)

Le 29/05/2017 à 09:04, Gavin Lambert via Boost a écrit :
On 29/05/2017 18:12, Vicente J. Botet Escriba wrote:
What your checked_outcome<T>::error will return if the is a exception_ptr? Ah, finally someone brought this up. Good!
It returns an error_code with bad_outcome_category and bad_outcome_errc::exception_present. Ugh. Why your design is never coherent. Why you don't name your functions after what they do? What if the exception_ptr contains an arbitrary exception? No way to obtain an error_code :( I believe your design is too much guided by your AFIO use case. Not all the applications use system errors.
I think you may have misinterpreted that. Niall's saying (if I haven't misinterpreted myself) that calling error() on an outcome that contains any arbitrary exception will give that one specific error code (which basically tells you that your code is probably wrong to have asked, but it's not a precondition violation). Of what specific error code are you talking about? How do you get one from exception_ptr?
That sounds like it's what you're wanting, and it's probably the behaviour I'd expect.
(There might be some argument that if the contained exception happens to be a std::system_error then it might be nice if the contained error_code could be extracted and returned, but that's not what Niall said and it sounds like that's what you're objecting to.) See above.
Vicente

2017-05-29 13:00 GMT+02:00 Vicente J. Botet Escriba via Boost < boost@lists.boost.org>:
Le 29/05/2017 à 09:04, Gavin Lambert via Boost a écrit :
On 29/05/2017 18:12, Vicente J. Botet Escriba wrote:
What your checked_outcome<T>::error will return if the is a
exception_ptr?
Ah, finally someone brought this up. Good!
It returns an error_code with bad_outcome_category and bad_outcome_errc::exception_present.
Ugh. Why your design is never coherent. Why you don't name your functions after what they do? What if the exception_ptr contains an arbitrary exception? No way to obtain an error_code :( I believe your design is too much guided by your AFIO use case. Not all the applications use system errors.
I think you may have misinterpreted that. Niall's saying (if I haven't misinterpreted myself) that calling error() on an outcome that contains any arbitrary exception will give that one specific error code (which basically tells you that your code is probably wrong to have asked, but it's not a precondition violation).
Of what specific error code are you talking about? How do you get one from exception_ptr?
No, he has one dedicate code for the occasion: error_type((int) monad_errc::exception_present, monad_category()) See: https://github.com/ned14/boost.outcome/blob/56c6ea31e1857e0ee415780b9a5f25ce... Regards, &rzej;

(There might be some argument that if the contained exception happens to be a std::system_error then it might be nice if the contained error_code could be extracted and returned, but that's not what Niall said and it sounds like that's what you're objecting to.)
The conversion of a thrown STL exception type to its equivalent error_code is an expensive operation. Something to be done only on request and with the API docs loudly proclaiming its expense. That said, Peter has proposed https://github.com/ned14/boost.outcome/issues/50 where I would assume I'll do a long sequence of dynamic_cast<> from the passed std::exception const& to deduce what its original type is, and thus the correct error_code. That may or may not be faster or slower than a long sequence of catch clauses, I'll benchmark before I decide. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

- error_type& .error_raw() - reinterpret_cast<error_type&>
evidently we don't like the _raw suffix.
.error_unsafe(), .unsafe_error(), .unchecked_error() all work for me too. For me operator*() and operator->() absolutely needs wide contracts. If people want the performance of narrow, they can manually write out excpt.value_raw()->var rather than excpt->var, or subclass expected with a narrow reimplementation of operator*(). It won't kill them.
Outcome distinctly categorises failure-due-to-error and failure-due-to-exception. I think that important given its different use case to Expected which is explicitly for low latency and C++ disabled environments. Do you mean exceptions disabled? How would you have exceptions when the exceptions are disabled?
But Expected as the STL type, I think your proposal is safe. Most of your users will throw exceptions for exceptional situations.
I guess that when exceptions are disabled, an implementation will just terminate the program, isn't it?.
Not at all. Indeed MSVC can still throw and catch exceptions with exceptions completely disabled so long as the throw is caught before leaving the stack frame in which the throw is done. For all major compilers, you can compile some files with C++ exceptions enabled, others not. exception_ptr is safe to transport thrown and caught C++ exceptions through exceptions disabled code. You can thus "bundle up" usage of the STL into islands kept separate from the rest of your code, and pass thrown and caught exceptions between islands of exceptions enabled C++ using Outcome.
I can not ignore this Outcome review, but I have not see too much people saying that the semantic you have done to this functions is the good one.
Strange. I've been surprised at how little criticism there has been of my design choices. Silence can be interpreted both ways I guess.
Nevertheless I will add an open point about having narrow and wide contract for the error access. I will add also another open point to have the possibility to return E{} when the contract is wide instead of throwing an exception.
IMHO we need just the narrow contract function. The other can be built on top of this by the user or by Outcome.
Remember Expected proposal has already two open_points for err error_or(exp, err) and bool has_error(exp, err)
I do agree with this philosophy, but if you're going to go narrow, you need to go narrow on everything including .value(). You need to consistently choose one or the other. No middle ground. An advantage of all narrow is you can eliminate bad_expected_access and simplify the Expected proposal to be merely a thin API convenience layer wrapping std::variant. A separate std::result, as Peter suggested, can layer on top of std::expected with all wide observers as would make much more sense for an object returning uncertainty. But it's up to you.
What your checked_outcome<T>::error will return if the is a exception_ptr? Ah, finally someone brought this up. Good!
It returns an error_code with bad_outcome_category and bad_outcome_errc::exception_present.
Ugh. Why your design is never coherent. Why you don't name your functions after what they do?
I would call my design asymmetrical. I can see why some would call it incoherent.
What if the exception_ptr contains an arbitrary exception? No way to obtain an error_code :(
There is a macro to do that in the presented library. Peter has discovered it to be over engineered given current compilers, and so https://github.com/ned14/boost.outcome/issues/50 will replace it.
I believe your design is too much guided by your AFIO use case. Not all the applications use system errors.
As the documentation tries to show, Outcome only makes sense for those users of expected<T, E> where E being hard coded to an error_code makes sense for them i.e. where causes of failure are unpredictable and come entirely from external code like the OS. Once you are in that territory, you are exactly where AFIO is as a low level system library, and Outcome suits very well. If you were application code with many disparate third party libraries with different error coding, it would also suit well. If you are application code with a single vision for all error possibilities, expected<T, E> is a better choice. If you are constexpr programming, expected<T, E> is literally the only game in town, Outcome can't work in constexpr. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

- error_type& .error_raw() - reinterpret_cast<error_type&> evidently we don't like the _raw suffix. .error_unsafe(), .unsafe_error(), .unchecked_error() all work for me too. How likes any of those? Who prefers error() to throw if there is a value? or return by value and return E{} when there is a value and I don't know hat when there is an exception_ptr?
For me operator*() and operator->() absolutely needs wide contracts. If people want the performance of narrow, they can manually write out excpt.value_raw()->var rather than excpt->var, or subclass expected with a narrow reimplementation of operator*(). It won't kill them. I cannot agree here; operator* and operator-> have narrow contracts. We must/should follows this design. If you want, you can have also a named function, but please, don't overload operator* and operator-> with similar meaning and different constraints.
Outcome distinctly categorises failure-due-to-error and failure-due-to-exception. I think that important given its different use case to Expected which is explicitly for low latency and C++ disabled environments. Do you mean exceptions disabled? How would you have exceptions when the exceptions are disabled? But Expected as the STL type, I think your proposal is safe. Most of your users will throw exceptions for exceptional situations. I guess that when exceptions are disabled, an implementation will just terminate the program, isn't it?. Not at all. Indeed MSVC can still throw and catch exceptions with exceptions completely disabled so long as the throw is caught before leaving the stack frame in which the throw is done. I was not aware of that. It will be great to have probes of all the things you are saying, having
Le 29/05/2017 à 17:05, Niall Douglas via Boost a écrit : the assembler and explaining how do you deduce your conclusions. Anyway, this doesn't help too much, isn't it? Could you give an example?
For all major compilers, you can compile some files with C++ exceptions enabled, others not. exception_ptr is safe to transport thrown and caught C++ exceptions through exceptions disabled code. You can thus "bundle up" usage of the STL into islands kept separate from the rest of your code, and pass thrown and caught exceptions between islands of exceptions enabled C++ using Outcome.
I see why you could want to store an exception_ptr in this case. I wonder just how often do we find it.
I can not ignore this Outcome review, but I have not see too much people saying that the semantic you have done to this functions is the good one. Strange. I've been surprised at how little criticism there has been of my design choices. Silence can be interpreted both ways I guess.
he he, you find that your library has not been criticized?
Nevertheless I will add an open point about having narrow and wide contract for the error access. I will add also another open point to have the possibility to return E{} when the contract is wide instead of throwing an exception.
IMHO we need just the narrow contract function. The other can be built on top of this by the user or by Outcome.
Remember Expected proposal has already two open_points for err error_or(exp, err) and bool has_error(exp, err) I do agree with this philosophy, but if you're going to go narrow, you need to go narrow on everything including .value(). You need to consistently choose one or the other. No middle ground.
value() id wide operator*() is narrow.
An advantage of all narrow is you can eliminate bad_expected_access and simplify the Expected proposal to be merely a thin API convenience layer wrapping std::variant. A separate std::result, as Peter suggested, can layer on top of std::expected with all wide observers as would make much more sense for an object returning uncertainty. But it's up to you.
I'm not against the the wide functions. I'm against not having the narrow one.
What your checked_outcome<T>::error will return if the is a exception_ptr? Ah, finally someone brought this up. Good!
It returns an error_code with bad_outcome_category and bad_outcome_errc::exception_present. Ugh. Why your design is never coherent. Why you don't name your functions after what they do? I would call my design asymmetrical. I can see why some would call it incoherent.
I believe that we want to know what a function does when we see a name that is used already. I call this coherency. Having functions that use the same name and that they do different things is not asymmetrical for me.
What if the exception_ptr contains an arbitrary exception? No way to obtain an error_code :( There is a macro to do that in the presented library. Peter has discovered it to be over engineered given current compilers, and so https://github.com/ned14/boost.outcome/issues/50 will replace it.
I see that Andrzej has responded in your place elsewhere. So you will use a single error code in this case, isn't it? More I know about your library more I find it wired. We can consider expected<T,E> as a Nullable type, but I will not because I will not make all the error values to be considered as a single null value. Vicente

On 30/05/2017 11:25, Vicente J. Botet Escriba wrote:
Le 29/05/2017 à 17:05, Niall Douglas a écrit :
- error_type& .error_raw() - reinterpret_cast<error_type&> evidently we don't like the _raw suffix. .error_unsafe(), .unsafe_error(), .unchecked_error() all work for me too. How likes any of those? Who prefers error() to throw if there is a value? or return by value and return E{} when there is a value and I don't know hat when there is an exception_ptr?
FWIW, my preferred options would be (which I think were the originals): - value() throws if holding error or exception (or empty) - error() returns E{} if holding value or E{errc::has_exception} if holding exception, or E{errc::no_value} if empty (names made up on the spot, doesn't matter) - exception() returns nullptr if holding value or error or empty These seem like safe and reasonable defaults and permits code to always extract an error_code or exception_ptr even on success, which can aid certain interop scenarios with older APIs and otherwise simplify caller logic. I would prefer that unchecked_* (or whatever) versions did not exist as then nobody would accidentally call them and perhaps introduce UB. But I'm not strongly opposed to them.

Gavin Lambert wrote:
FWIW, my preferred options would be (which I think were the originals):
- value() throws if holding error or exception (or empty)
- error() returns E{} if holding value or E{errc::has_exception} if holding exception, or E{errc::no_value} if empty (names made up on the spot, doesn't matter)
- exception() returns nullptr if holding value or error or empty
I like these as well.

On 30/05/2017 11:25, Vicente J. Botet Escriba wrote:
Le 29/05/2017 à 17:05, Niall Douglas a écrit :
- error_type& .error_raw() - reinterpret_cast<error_type&> evidently we don't like the _raw suffix. .error_unsafe(), .unsafe_error(), .unchecked_error() all work for me too. How likes any of those? Who prefers error() to throw if there is a value? or return by value and return E{} when there is a value and I don't know hat when there is an exception_ptr?
FWIW, my preferred options would be (which I think were the originals): Thanks, you are the first (I believe) that supported the proposed interface, but maybe I'm wrong.
- value() throws if holding error or exception (or empty)
- error() returns E{} if holding value or E{errc::has_exception} if holding exception, or E{errc::no_value} if empty (names made up on the spot, doesn't matter) Would you like to extract the error code from the stored exception If
Le 30/05/2017 à 01:41, Gavin Lambert via Boost a écrit : the exception had an error_code() function? So the error codes must contain also the success codes. And now we don't know if errc::no_value means success or failure. Wondering id make_error_code isn't more appropriated. I know you don't like long names, but this is what the function is doing.
- exception() returns nullptr if holding value or error or empty
Why not create an exception when there is not, for the other cases? When you say that return nullptr you mean that it returns an exception_ptr with a nullptr in. This seems like safe and reasonable default and permit code to always extract a exception even on success. Without this interface we can say the it is UB to have an exception_ptr with a nullptr stored in an outcome. Now that we can get one of those, it is much difficult to reason about.
These seem like safe and reasonable defaults and permits code to always extract an error_code or exception_ptr even on success, which can aid certain interop scenarios with older APIs and otherwise simplify caller logic. I agree with you "This seem like safe" but I'm not sure it is. I'm not against the semantic of these wide functions and even the one I gave above. If people prefer the shorter names for the wide contract I could live with that. But I would like a single semantic for functions with the same name.
I would prefer that unchecked_* (or whatever) versions did not exist as then nobody would accidentally call them and perhaps introduce UB. But I'm not strongly opposed to them.
They introduce UB of course and static analysis tools are there to check for some of the most current cases even before I run my program. This allows to have a sorter program that is more understandable. At run-time the library could use some kind of contract programming so that the checks are done when configured and so you get the bad usage as soon as possible. So you are not one of those that needs the more efficient interface. When you know the precondition is satisfied, why do you want to pay for additional checks on the library and on the user code? If you provide the minimal interface the user can built on top of it whatever he needs. Not providing it will imply that all will pay for. At the end what we need is the SumType interface: alternative index and direct narrow access and/or visitation of a variant< ...> and some kind of trait that associates an index to success or failure. Vicente

On 30/05/2017 18:45, Vicente J. Botet Escriba wrote:
Le 30/05/2017 à 01:41, Gavin Lambert a écrit :
FWIW, my preferred options would be (which I think were the originals):
Thanks, you are the first (I believe) that supported the proposed interface, but maybe I'm wrong.
I think my suggestions aren't quite identical (in particular I think empty is handled differently in some cases). But they're close.
- value() throws if holding error or exception (or empty)
- error() returns E{} if holding value or E{errc::has_exception} if holding exception, or E{errc::no_value} if empty (names made up on the spot, doesn't matter)
Would you like to extract the error code from the stored exception If the exception had an error_code() function?
If that's fast and noexcept, maybe. Otherwise it's probably not worthwhile. It's probably a coding error for a method that wants error_codes to call something that throws system_errors.
So the error codes must contain also the success codes. And now we don't know if errc::no_value means success or failure.
Niall's stated in other threads that in actual usage, sometimes empty is a success and sometimes it's a failure. So the decision of which it is has to be left to the caller, who presumably knows what it actually means. I expect that in a handling-empty code path, error() would never be called, or if called, would be stored in a variable that is later discarded. But the suggested behaviour makes it safe to use even if the code doesn't check for empty and does make use of the error code.
Wondering id make_error_code isn't more appropriated. I know you don't like long names, but this is what the function is doing.
In this context I'm describing behaviour absent of any judgement on what the things are actually called. Though FWIW both names are a little suspect since you'd expect make_error_code to actually return a std::error_code. make_unexpected from your proposal is more sensible in that regard; I suppose make_error_result or something like that would be more correct in this case. Doesn't really matter too much as people can "using" bikeshed their own names in fairly easily.
- exception() returns nullptr if holding value or error or empty
Why not create an exception when there is not, for the other cases?
I can see an argument for returning a std::exception_ptr containing a std::system_error in the case where there is an error code. However I'm not convinced that this is the correct choice. The purpose of having an error code is to indicate non-exceptional failure, so synthesizing an exception from one in this case seems dubious. Additionally, at least in MSVC any thrown exception (or exception captured by exception_ptr and rethrown, to a more limited extent) automatically carries a context from the original throw site (which can be turned into a stack trace). While this is still somewhat true of an exception_ptr constructed after the fact, it will capture the context at that point and not where the error was originally raised, which is perhaps misleading. Similarly for first-chance debugger intercepts. One possible advantage of doing that construction is that you could have code like this: if (r.has_error()) { std::rethrow_exception(r.exception()); } But without that, it's still possible with: if (r.has_error()) { (void) r.value(); /* will throw */ } (Slight downside of this is that the compiler has to inline and walk into value() before realising that it always throws, so it might emit warnings unless you add some extra boilerplate.)
When you say that return nullptr you mean that it returns an exception_ptr with a nullptr in.
I meant literally return nullptr (exception_ptr is a NullablePointer so it's implicitly constructible from nullptr_t), but yes, it's basically the same thing, and the same as a default-constructed exception_ptr.
Without this interface we can say the it is UB to have an exception_ptr with a nullptr stored in an outcome. Now that we can get one of those, it is much difficult to reason about.
Reasoning about defined behaviour is surely easier than reasoning about undefined behaviour?
But I would like a single semantic for functions with the same name.
What does this mean? I'm not sure how it applies here as we're talking about methods with different names.
So you are not one of those that needs the more efficient interface. When you know the precondition is satisfied, why do you want to pay for additional checks on the library and on the user code? If you provide the minimal interface the user can built on top of it whatever he needs. Not providing it will imply that all will pay for.
Perhaps I am mistaken, but I generally prefer an overly-defensive coding style with explicit precondition checks on public interfaces. I don't trust developers (including myself) to write correct code and I don't trust them to run static analysers on their incorrect code.

Mere moments ago, quoth I:
But without that, it's still possible with:
if (r.has_error()) { (void) r.value(); /* will throw */ }
(Slight downside of this is that the compiler has to inline and walk into value() before realising that it always throws, so it might emit warnings unless you add some extra boilerplate.)
Though to be fair, this is a bit contrived in isolation; it's likely that code that makes use of that behaviour would more naturally be written as: auto value = do_something().value(); // that threw if empty, error, or exception, // so now you can safely play with value. The main point being that I'm not sure how useful it would be to have .exception() contain a synthetic exception in the non-thrown cases. (But then, normally you don't want to catch and transport exceptions at all, which is why result<T> exists. outcome<T> and exception transport only becomes useful when you're about to exit from an exception-enabled island into a sea of noexcept, to use Niall's phrasing.)

Le 30/05/2017 à 09:27, Gavin Lambert via Boost a écrit :
On 30/05/2017 18:45, Vicente J. Botet Escriba wrote:
Le 30/05/2017 à 01:41, Gavin Lambert a écrit :
FWIW, my preferred options would be (which I think were the originals):
Thanks, you are the first (I believe) that supported the proposed interface, but maybe I'm wrong.
I think my suggestions aren't quite identical (in particular I think empty is handled differently in some cases). But they're close. I don't want to have anything that sometimes is success and sometimes is failure.
- value() throws if holding error or exception (or empty)
- error() returns E{} if holding value or E{errc::has_exception} if holding exception, or E{errc::no_value} if empty (names made up on the spot, doesn't matter)
Would you like to extract the error code from the stored exception If the exception had an error_code() function?
If that's fast and noexcept, maybe. Otherwise it's probably not worthwhile. It's probably a coding error for a method that wants error_codes to call something that throws system_errors.
So the error codes must contain also the success codes. And now we don't know if errc::no_value means success or failure.
Niall's stated in other threads that in actual usage, sometimes empty is a success and sometimes it's a failure. So the decision of which it is has to be left to the caller, who presumably knows what it actually means.
I expect that in a handling-empty code path, error() would never be called, or if called, would be stored in a variable that is later discarded. But the suggested behaviour makes it safe to use even if the code doesn't check for empty and does make use of the error code.
Wondering id make_error_code isn't more appropriated. I know you don't like long names, but this is what the function is doing.
In this context I'm describing behaviour absent of any judgement on what the things are actually called. Though FWIW both names are a little suspect since you'd expect make_error_code to actually return a std::error_code. And it will.
error-code ec = r.make_error_code();
make_unexpected from your proposal is more sensible in that regard; I suppose make_error_result or something like that would be more correct in this case. I was talking of a factory from the result or the outcome. It was a renaming of your error()
Doesn't really matter too much as people can "using" bikeshed their own names in fairly easily.
- exception() returns nullptr if holding value or error or empty
Why not create an exception when there is not, for the other cases?
I can see an argument for returning a std::exception_ptr containing a std::system_error in the case where there is an error code.
However I'm not convinced that this is the correct choice. The purpose of having an error code is to indicate non-exceptional failure, so synthesizing an exception from one in this case seems dubious. If the purpose of error is to indicate an non exceptional error, why do we want to add error codes that mean value, no_value and exception?
Additionally, at least in MSVC any thrown exception (or exception captured by exception_ptr and rethrown, to a more limited extent) automatically carries a context from the original throw site (which can be turned into a stack trace). While this is still somewhat true of an exception_ptr constructed after the fact, it will capture the context at that point and not where the error was originally raised, which is perhaps misleading. Similarly for first-chance debugger intercepts.
One possible advantage of doing that construction is that you could have code like this:
if (r.has_error()) { std::rethrow_exception(r.exception()); }
But without that, it's still possible with:
if (r.has_error()) { (void) r.value(); /* will throw */ } r.value(); // ;-)
(Slight downside of this is that the compiler has to inline and walk into value() before realising that it always throws, so it might emit warnings unless you add some extra boilerplate.)
When you say that return nullptr you mean that it returns an exception_ptr with a nullptr in.
I meant literally return nullptr (exception_ptr is a NullablePointer so it's implicitly constructible from nullptr_t), but yes, it's basically the same thing, and the same as a default-constructed exception_ptr. I know. I wanted to state that your are returning something that could already be stored in a exception_ptr, and so the ambiguity starts. I believe that outcome should store not_null_exception_ptr instead of exception_ptr. not_null_exception_ptr is a thin class around exception_ptr that doesn't provide default constructor.
Without this interface we can say the it is UB to have an exception_ptr with a nullptr stored in an outcome. Now that we can get one of those, it is much difficult to reason about.
Reasoning about defined behaviour is surely easier than reasoning about undefined behaviour? You don't need to reason about UB in your program, while you need to for defined behavior. This means more code.
But I would like a single semantic for functions with the same name.
What does this mean? I'm not sure how it applies here as we're talking about methods with different names. You have error() functions for outcome, result and expected. Each one doing one different thing.
So you are not one of those that needs the more efficient interface. When you know the precondition is satisfied, why do you want to pay for additional checks on the library and on the user code? If you provide the minimal interface the user can built on top of it whatever he needs. Not providing it will imply that all will pay for.
Perhaps I am mistaken, but I generally prefer an overly-defensive coding style with explicit precondition checks on public interfaces. I don't trust developers (including myself) to write correct code and I don't trust them to run static analysers on their incorrect code. We don't play in the same game. I'm not against safety, but I need sometimes to master the efficiency. It is a pity if you cannot use these classes because you are paying for what you don't use. A library must provide the minimum, and the minimum is a narrow contract.
You could add the static analyzer run on the Continuous Integration (CI) or as a necessary check before merging to the develop branch. You can run your CI tests with sanitizers. Vicente

On 31/05/2017 03:39, Vicente J. Botet Escriba wrote:
Le 30/05/2017 à 09:27, Gavin Lambert a écrit :
I think my suggestions aren't quite identical (in particular I think empty is handled differently in some cases). But they're close. I don't want to have anything that sometimes is success and sometimes is failure.
That's unavoidable for an empty state. It's like a NULL in a database or the none_t state in an optional -- it's up to whoever uses it to decide "the value is missing, and that's bad because it should have had one" or "the value is missing but that's ok, it doesn't have to have one".
In this context I'm describing behaviour absent of any judgement on what the things are actually called. Though FWIW both names are a little suspect since you'd expect make_error_code to actually return a std::error_code. And it will.
error-code ec = r.make_error_code();
What? No, that's not what I was talking about at all. That's just silly.
But without that, it's still possible with:
if (r.has_error()) { (void) r.value(); /* will throw */ } r.value(); // ;-)
Your point being?
I meant literally return nullptr (exception_ptr is a NullablePointer so it's implicitly constructible from nullptr_t), but yes, it's basically the same thing, and the same as a default-constructed exception_ptr. I know. I wanted to state that your are returning something that could already be stored in a exception_ptr, and so the ambiguity starts. I believe that outcome should store not_null_exception_ptr instead of exception_ptr. not_null_exception_ptr is a thin class around exception_ptr that doesn't provide default constructor.
That's even worse.
What does this mean? I'm not sure how it applies here as we're talking about methods with different names. You have error() functions for outcome, result and expected. Each one doing one different thing.
No, each one returns an error_code. They do exactly the same thing in all cases. (Except perhaps expected, but that's because it's aiming to follow a standard that might mandate different behaviour.)

On 31/05/2017 03:39, Vicente J. Botet Escriba wrote:
Le 30/05/2017 à 09:27, Gavin Lambert a écrit :
I think my suggestions aren't quite identical (in particular I think empty is handled differently in some cases). But they're close. I don't want to have anything that sometimes is success and sometimes is failure.
That's unavoidable for an empty state. It's like a NULL in a database or the none_t state in an optional -- it's up to whoever uses it to decide "the value is missing, and that's bad because it should have had one" or "the value is missing but that's ok, it doesn't have to have one". When I define functor::map/transform or monad::bind/chain, I need to know what is the value_type if the functor or the monad. If empty is
Le 31/05/2017 à 01:14, Gavin Lambert via Boost a écrit : part of the success alternatives it is part of the value_type. If success is part of the failure, it isn't part of the value_type. If we don't define if it is a success or a failure we are unable to define these functions.
In this context I'm describing behaviour absent of any judgement on what the things are actually called. Though FWIW both names are a little suspect since you'd expect make_error_code to actually return a std::error_code. And it will.
error-code ec = r.make_error_code();
What? No, that's not what I was talking about at all. That's just silly.
But it was my that was talking about that and you didn't understood. This is exactly what the function is doing. Building an error_code from an outcome or a result.
But without that, it's still possible with:
if (r.has_error()) { (void) r.value(); /* will throw */ } r.value(); // ;-)
Your point being?
It is the same. No need to check as value() do it already.
I meant literally return nullptr (exception_ptr is a NullablePointer so it's implicitly constructible from nullptr_t), but yes, it's basically the same thing, and the same as a default-constructed exception_ptr. I know. I wanted to state that your are returning something that could already be stored in a exception_ptr, and so the ambiguity starts. I believe that outcome should store not_null_exception_ptr instead of exception_ptr. not_null_exception_ptr is a thin class around exception_ptr that doesn't provide default constructor.
That's even worse.
Why this is worse? We use the type system to prevent from user error.
What does this mean? I'm not sure how it applies here as we're talking about methods with different names. You have error() functions for outcome, result and expected. Each one doing one different thing.
No, each one returns an error_code. They do exactly the same thing in all cases. (Except perhaps expected, but that's because it's aiming to follow a standard that might mandate different behaviour.)
No, some returns the stored error code and others can calculate one that doesn't correspond to the error that was transported. Not all the functions that return an error_code must be named equal. A function must convey the intent. Best, Vicente

On 31/05/2017 11:47, Vicente J. Botet Escriba wrote:
That's unavoidable for an empty state. It's like a NULL in a database or the none_t state in an optional -- it's up to whoever uses it to decide "the value is missing, and that's bad because it should have had one" or "the value is missing but that's ok, it doesn't have to have one". When I define functor::map/transform or monad::bind/chain, I need to know what is the value_type if the functor or the monad. If empty is
Le 31/05/2017 à 01:14, Gavin Lambert a écrit : part of the success alternatives it is part of the value_type. If success is part of the failure, it isn't part of the value_type. If we don't define if it is a success or a failure we are unable to define these functions.
Why do we care about monads?
But without that, it's still possible with:
if (r.has_error()) { (void) r.value(); /* will throw */ } r.value(); // ;-)
Your point being? It is the same. No need to check as value() do it already.
Well, ok. That's basically what I said in my followup post. It was a contrived example constructed to be similar to the one that preceded it (thereby explaining why it is not necessary to have exception() return an exception in any but the actually-transporting-an-exception case).
Why this is worse? We use the type system to prevent from user error.
Having no exception is not an error. Asking for the exception is also not an error, because the exception can represent a state that means "I don't have one". This does require returning the exception_ptr by value. Is that what you're really objecting to? Or are you just too wedded to the semantics of variant? Like I said in the other thread, while I agree that the internal storage can be formed from a variant, logically the behaviour should not be that of a variant. Otherwise you'd just use a variant.
No, each one returns an error_code. They do exactly the same thing in all cases. (Except perhaps expected, but that's because it's aiming to follow a standard that might mandate different behaviour.) No, some returns the stored error code and others can calculate one that doesn't correspond to the error that was transported. Not all the functions that return an error_code must be named equal. A function must convey the intent.
They return an error_code that represents the state of the object. This is either the actual transported error_code, or an error_code that means "success", "an exception occurred", or "no value present". This does not seem weird in any way to me. Note that expected<T,E> can't do that, because it doesn't know how to construct an E that represents anything; it can only return what was transported or fail (either by UB or throw). Thus expected<T,E> is forced to just be a plain variant with narrow interface. This makes it a good building block but a bad end-user type. This is presumably what motivated Niall to write result<T> and outcome<T> in the first place; I believe he's even said so.

Le 31/05/2017 à 02:01, Gavin Lambert via Boost a écrit :
On 31/05/2017 11:47, Vicente J. Botet Escriba wrote:
That's unavoidable for an empty state. It's like a NULL in a database or the none_t state in an optional -- it's up to whoever uses it to decide "the value is missing, and that's bad because it should have had one" or "the value is missing but that's ok, it doesn't have to have one". When I define functor::map/transform or monad::bind/chain, I need to know what is the value_type if the functor or the monad. If empty is
Le 31/05/2017 à 01:14, Gavin Lambert a écrit : part of the success alternatives it is part of the value_type. If success is part of the failure, it isn't part of the value_type. If we don't define if it is a success or a failure we are unable to define these functions.
Why do we care about monads?
We care about extensibility of the proposed classes, isn't it? We should care about Monads, because because the functions returning outcome or result are monadic functions and these functions don't compose well without the monad interface. This will avoid writing flat code. No raw loops. When you compose function that return a Monad instead of the value type (and in this case outcome<T> and result<T> are wrapping the value type in one way or another), your functions don't compose any more. You need another kind of function composition provided by a monad interface (monad::compose()) One thing is essential. To identify what do we consider the result when computation succeeds. If empty means success the value type is something like optional<T>. If its is a failure the it is T. Letting the user an interpretation of empty doesn't helps in this task. We should have both cases with different names.
Why this is worse? We use the type system to prevent from user error.
Having no exception is not an error. Asking for the exception is also not an error, because the exception can represent a state that means "I don't have one".
This does require returning the exception_ptr by value. Is that what you're really objecting to? Or are you just too wedded to the semantics of variant? Like I said in the other thread, while I agree that the internal storage can be formed from a variant, logically the behaviour should not be that of a variant. Otherwise you'd just use a variant.
If you consider that one of the valid states of outcome is an exception_ptr without a exception, and outcome::exception() returns the transported exception_ptr or an exception_ptr without exception the user is unable to know exactly what was reported. Maybe you don't need more, but I believe we need to have an access to the transported failure with a simple function. Could you show me an example of use of exception() that make the code simpler?
No, each one returns an error_code. They do exactly the same thing in all cases. (Except perhaps expected, but that's because it's aiming to follow a standard that might mandate different behaviour.) No, some returns the stored error code and others can calculate one that doesn't correspond to the error that was transported. Not all the functions that return an error_code must be named equal. A function must convey the intent.
They return an error_code that represents the state of the object. This is either the actual transported error_code, or an error_code that means "success", "an exception occurred", or "no value present". This does not seem weird in any way to me.
Note that expected<T,E> can't do that, because it doesn't know how to construct an E that represents anything; it can only return what was transported or fail (either by UB or throw). Thus expected<T,E> is forced to just be a plain variant with narrow interface. This makes it a good building block but a bad end-user type. This is presumably what motivated Niall to write result<T> and outcome<T> in the first place; I believe he's even said so.
So if expected, that is class close to outcome and result, uses error to access the transported error, outcome and result should use another name to return an error_code that represents the state of the object. We can as well change expected::error to use another name. Why do you want to use the same name for different things? Vicente

2017-05-30 9:27 GMT+02:00 Gavin Lambert via Boost <boost@lists.boost.org>:
On 30/05/2017 18:45, Vicente J. Botet Escriba wrote:
Le 30/05/2017 à 01:41, Gavin Lambert a écrit :
FWIW, my preferred options would be (which I think were the originals):
Thanks, you are the first (I believe) that supported the proposed interface, but maybe I'm wrong.
I think my suggestions aren't quite identical (in particular I think empty is handled differently in some cases). But they're close.
- value() throws if holding error or exception (or empty)
- error() returns E{} if holding value or E{errc::has_exception} if holding exception, or E{errc::no_value} if empty (names made up on the spot, doesn't matter)
Would you like to extract the error code from the stored exception If the exception had an error_code() function?
If that's fast and noexcept, maybe. Otherwise it's probably not worthwhile. It's probably a coding error for a method that wants error_codes to call something that throws system_errors.
So the error codes must contain also the success codes. And now we don't
know if errc::no_value means success or failure.
Niall's stated in other threads that in actual usage, sometimes empty is a success and sometimes it's a failure. So the decision of which it is has to be left to the caller, who presumably knows what it actually means.
I expect that in a handling-empty code path, error() would never be called, or if called, would be stored in a variable that is later discarded. But the suggested behaviour makes it safe to use even if the code doesn't check for empty and does make use of the error code.
Wondering id make_error_code isn't more appropriated. I know you don't
like long names, but this is what the function is doing.
In this context I'm describing behaviour absent of any judgement on what the things are actually called. Though FWIW both names are a little suspect since you'd expect make_error_code to actually return a std::error_code. make_unexpected from your proposal is more sensible in that regard; I suppose make_error_result or something like that would be more correct in this case.
Doesn't really matter too much as people can "using" bikeshed their own names in fairly easily.
- exception() returns nullptr if holding value or error or empty
Why not create an exception when there is not, for the other cases?
I can see an argument for returning a std::exception_ptr containing a std::system_error in the case where there is an error code.
However I'm not convinced that this is the correct choice. The purpose of having an error code is to indicate non-exceptional failure, so synthesizing an exception from one in this case seems dubious.
That is an important observation. Would everyone in this discussion agree that whenever there is an incorrect usage of `outcome` detected (like extacting vaue when we are storing an error code, or extracting error code when we are storing an exception_ptr) it is uniformly signaled by BOOST_THROW_EXCEPTION() (or some such)? No E{}, no translations: just BOOST_THROW_EXCEPTION()?
Without this interface we can say the it is UB to have an exception_ptr
with a nullptr stored in an outcome. Now that we can get one of those, it is much difficult to reason about.
Reasoning about defined behaviour is surely easier than reasoning about undefined behaviour?
No. When you know some states of the object are illegal, you can easily tell when someone is using the library incorrectly. And you can fis the pfoblem more quiclkly.
But I would like a single semantic for functions with the same name.
What does this mean? I'm not sure how it applies here as we're talking about methods with different names.
So you are not one of those that needs the more efficient interface. When
you know the precondition is satisfied, why do you want to pay for additional checks on the library and on the user code? If you provide the minimal interface the user can built on top of it whatever he needs. Not providing it will imply that all will pay for.
Perhaps I am mistaken, but I generally prefer an overly-defensive coding style with explicit precondition checks on public interfaces. I don't trust developers (including myself) to write correct code and I don't trust them to run static analysers on their incorrect code.
Regards, &rzej;

Gavin Lambert wrote:
- error() returns E{} if holding value or E{errc::has_exception} if holding exception, or E{errc::no_value} if empty (names made up on the spot, doesn't matter)
The behavior of returning {errc::no_value} (or, as I called it, {errc::uninitialized}) on empty incidentally matches that of a never-empty result that initializes itself to {errc::no_value} on default construction, as I suggested. Note how in both cases, error() on a default-constructed result would return {errc::no_value}, and the behavior of value() and exception() (for outcome) is the same, too. If you provide empty() for the never-empty that returns error() == errc::no_value, that will match as well. The only difference would be that has_error will be false for the empty result and true for the non-empty one.

FWIW, my preferred options would be (which I think were the originals):
- value() throws if holding error or exception (or empty)
- error() returns E{} if holding value or E{errc::has_exception} if holding exception, or E{errc::no_value} if empty (names made up on the spot, doesn't matter)
The current behaviour is "always throw on observing empty". That's what makes empty special (and it traps unintentional propagation of empty).
- exception() returns nullptr if holding value or error or empty
The current behaviour is to return null exception_ptr if valued (so if(eptr) would be false i.e. "no exception here"), std::make_exception_ptr(std::system_error(error())) if errored, throw on empty. So an errored state is also an excepted state, but an excepted state is never an errored state. has_exception() returns true for either errored or excepted states. std::make_exception_ptr(std::system_error(error())) is fairly cheap, a few thousand CPU cycles. std::exception_ptr's are heavy anyway.
I would prefer that unchecked_* (or whatever) versions did not exist as then nobody would accidentally call them and perhaps introduce UB. But I'm not strongly opposed to them.
The __built_unreachable() is what convinced me they have merit. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-30 15:18 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
FWIW, my preferred options would be (which I think were the originals):
- value() throws if holding error or exception (or empty)
- error() returns E{} if holding value or E{errc::has_exception} if holding exception, or E{errc::no_value} if empty (names made up on the spot, doesn't matter)
The current behaviour is "always throw on observing empty". That's what makes empty special (and it traps unintentional propagation of empty).
- exception() returns nullptr if holding value or error or empty
The current behaviour is to return null exception_ptr if valued (so if(eptr) would be false i.e. "no exception here"), std::make_exception_ptr(std::system_error(error())) if errored, throw on empty.
So an errored state is also an excepted state, but an excepted state is never an errored state. has_exception() returns true for either errored or excepted states.
But is there some mental model behind this decision?
std::make_exception_ptr(std::system_error(error())) is fairly cheap, a few thousand CPU cycles. std::exception_ptr's are heavy anyway.
Or are these decisions, what to return in tricky cases are only driven by performance? Regards, &rzej;

On 30/05/2017 21:12, Andrzej Krzemienski via Boost wrote:
2017-05-30 15:18 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
- value() throws if holding error or exception (or empty)
- error() returns E{} if holding value or E{errc::has_exception} if holding exception, or E{errc::no_value} if empty (names made up on the spot, doesn't matter)
The current behaviour is "always throw on observing empty". That's what makes empty special (and it traps unintentional propagation of empty).
- exception() returns nullptr if holding value or error or empty
The current behaviour is to return null exception_ptr if valued (so if(eptr) would be false i.e. "no exception here"), std::make_exception_ptr(std::system_error(error())) if errored, throw on empty.
So an errored state is also an excepted state, but an excepted state is never an errored state. has_exception() returns true for either errored or excepted states.
But is there some mental model behind this decision?
Of course. exception <= error < value. So errors are exceptions, but exceptions are not errors. empty is the wildcard. It is modelled like NaN in floating-point, it propagates in a dominant fashion unless you explicitly deal with it.
std::make_exception_ptr(std::system_error(error())) is fairly cheap, a few thousand CPU cycles. std::exception_ptr's are heavy anyway.
Or are these decisions, what to return in tricky cases are only driven by performance?
If you are using result<T> instead of outcome<T>, it is assumed that you must feel an exception_ptr to be too expensive and/or you are not using exceptions at all. outcome<T> implicitly converts from result<T> on demand, so some code you can write without exception_ptr, some with, and the two interoperate seamlessly by all funnelling into outcome<T>. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-31 12:47 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
On 30/05/2017 21:12, Andrzej Krzemienski via Boost wrote:
2017-05-30 15:18 GMT+02:00 Niall Douglas via Boost < boost@lists.boost.org>:
- value() throws if holding error or exception (or empty)
- error() returns E{} if holding value or E{errc::has_exception} if holding exception, or E{errc::no_value} if empty (names made up on the spot, doesn't matter)
The current behaviour is "always throw on observing empty". That's what makes empty special (and it traps unintentional propagation of empty).
- exception() returns nullptr if holding value or error or empty
The current behaviour is to return null exception_ptr if valued (so if(eptr) would be false i.e. "no exception here"), std::make_exception_ptr(std::system_error(error())) if errored, throw on empty.
So an errored state is also an excepted state, but an excepted state is never an errored state. has_exception() returns true for either errored or excepted states.
But is there some mental model behind this decision?
Of course.
exception <= error < value. So errors are exceptions, but exceptions are not errors.
I still don't get it. Maybe what you are saying that "error" refers to both error_codes and exception_ptr-s, whereas "exception" refers to exception_ptr-s exclusively?
empty is the wildcard. It is modelled like NaN in floating-point, it propagates in a dominant fashion unless you explicitly deal with it.
std::make_exception_ptr(std::system_error(error())) is fairly cheap, a few thousand CPU cycles. std::exception_ptr's are heavy anyway.
Or are these decisions, what to return in tricky cases are only driven by performance?
If you are using result<T> instead of outcome<T>, it is assumed that you must feel an exception_ptr to be too expensive and/or you are not using exceptions at all. outcome<T> implicitly converts from result<T> on demand, so some code you can write without exception_ptr, some with, and the two interoperate seamlessly by all funnelling into outcome<T>.
Yes, this has always been clear from the docs. Regards, &rzej;

On 31/05/2017 12:02, Andrzej Krzemienski via Boost wrote:
2017-05-31 12:47 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
But is there some mental model behind this decision?
Of course.
exception <= error < value. So errors are exceptions, but exceptions are not errors.
I still don't get it. Maybe what you are saying that "error" refers to both error_codes and exception_ptr-s, whereas "exception" refers to exception_ptr-s exclusively?
Other way round. Exception refer to exception|error. Error refers to just error. If you need a motivating example, imagine a user accumulates result<T>'s from other code it calls into outcome<T>'s, and then does .exception() on the outcome<T>'s. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-31 15:17 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
On 31/05/2017 12:02, Andrzej Krzemienski via Boost wrote:
2017-05-31 12:47 GMT+02:00 Niall Douglas via Boost < boost@lists.boost.org>:
But is there some mental model behind this decision?
Of course.
exception <= error < value. So errors are exceptions, but exceptions are not errors.
I still don't get it. Maybe what you are saying that "error" refers to both error_codes and exception_ptr-s, whereas "exception" refers to exception_ptr-s exclusively?
Other way round. Exception refer to exception|error. Error refers to just error.
Ouch.
If you need a motivating example, imagine a user accumulates result<T>'s from other code it calls into outcome<T>'s, and then does .exception() on the outcome<T>'s.
Yes, I can understand why one wants to treat error_code and exception_ptr uniformly. But recognizing an error_code in `exception()` and not recognizing an exception_ptr in `error()` looks quite arbitrary to me: not guided ba an intuitive mental model. Regards, &rzej;

On 31/05/2017 14:25, Andrzej Krzemienski via Boost wrote:
2017-05-31 15:17 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
If you need a motivating example, imagine a user accumulates result<T>'s from other code it calls into outcome<T>'s, and then does .exception() on the outcome<T>'s.
Yes, I can understand why one wants to treat error_code and exception_ptr uniformly. But recognizing an error_code in `exception()` and not recognizing an exception_ptr in `error()` looks quite arbitrary to me: not guided ba an intuitive mental model.
Not at all. Errors are not exceptional. They are expected failure. Exceptions are exceptional. They are **un**expected failure. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-31 15:45 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
On 31/05/2017 14:25, Andrzej Krzemienski via Boost wrote:
2017-05-31 15:17 GMT+02:00 Niall Douglas via Boost < boost@lists.boost.org>:
If you need a motivating example, imagine a user accumulates result<T>'s from other code it calls into outcome<T>'s, and then does .exception() on the outcome<T>'s.
Yes, I can understand why one wants to treat error_code and exception_ptr uniformly. But recognizing an error_code in `exception()` and not recognizing an exception_ptr in `error()` looks quite arbitrary to me: not guided ba an intuitive mental model.
Not at all.
I have a problem combining these two replies:
Errors are not exceptional. They are expected failure.
Exceptions are exceptional. They are **un**expected failure.
And this one: Let me repeat: **exceptional** means either error_code or exception_ptr.
**error** means only error_code.
You can think of it like this: failure to explicitly check for an error results in an exceptional circumstance.
Errors -- usual failure, Exceptions -- exceptional failure. Ok. But when I call `error()` it is like saying "tell me if a *usual* failure ocurred", I get no exception, fine. When I call `exception()` it is like saying "tell me if an *exceptional* failure ocurred", and you are giving me a non-exceptional one. This does not (yet) sound consistent to me. Regards, &rzej;

On 31/05/2017 14:59, Andrzej Krzemienski via Boost wrote:
2017-05-31 15:45 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>: Errors -- usual failure, Exceptions -- exceptional failure. Ok. But when I call `error()` it is like saying "tell me if a *usual* failure ocurred", I get no exception, fine. When I call `exception()` it is like saying "tell me if an *exceptional* failure ocurred", and you are giving me a non-exceptional one.
This does not (yet) sound consistent to me.
It makes sense when one assumes that outcome<T> accumulates result<T>, which it does. Outcome's design assumes that all outcome types eventually end up in an outcome<T>. The programmer, when working with outcome<T>, knows that calling .exception() will return the exact same exception as would be thrown when calling .value(), thus saving wrapping .value() in a try...catch just to capture the exception_ptr emitted. The actually odd one out here is .error(), as Emil correctly spotted. It is there basically for the programmer to more efficiently intercept when an error code to exception throw conversion would occur, which is expensive. So, for outcome<T>, start with assuming that .value() and .exception() fully represent the twin sides of behaviour, and .error() is an early out mechanism. For result<T>, it's basically a castrated outcome<T> used only for performance sensitive code, or rather, to say "this function takes performance very seriously". Does this make more sense? Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

On 1/06/2017 08:17, Niall Douglas wrote:
The programmer, when working with outcome<T>, knows that calling .exception() will return the exact same exception as would be thrown when calling .value(), thus saving wrapping .value() in a try...catch just to capture the exception_ptr emitted.
Except empty. value() throws that as an exception. exception() also throws that as an exception (not returning it), which means that the programmer can't make that assumption you just claimed. Maybe that's what you want (as it leads very quickly to empty == std::terminate), but it's not consistent with use of empty as a success return as you've suggested in a few places.
For result<T>, it's basically a castrated outcome<T> used only for performance sensitive code, or rather, to say "this function takes performance very seriously".
From the docs and discussion thus far, this is not the impression I had. The impression I had was that result<T> should be used everywhere in inner exception-enabled code by preference (since exceptions should not be caught, they should be propagated outwards). And then up at the entrypoint from the sea of noexcept to the exception-enabled island, that entrypoint (which is itself noexcept) should return an outcome<> from the result<> of its children or an internal try-catch to propagate out an exception_ptr. outcome<> is used almost exclusively within the sea; result<> only rarely (for cases where an exception cannot occur). Further up the call stack (back in exception-throwing land) you receive an outcome<> and immediately unpack and rethrow any exception, or otherwise go back to returning result<>s.

On 01/06/2017 00:35, Gavin Lambert via Boost wrote:
On 1/06/2017 08:17, Niall Douglas wrote:
The programmer, when working with outcome<T>, knows that calling .exception() will return the exact same exception as would be thrown when calling .value(), thus saving wrapping .value() in a try...catch just to capture the exception_ptr emitted.
Except empty. value() throws that as an exception. exception() also throws that as an exception (not returning it), which means that the programmer can't make that assumption you just claimed.
Maybe that's what you want (as it leads very quickly to empty == std::terminate), but it's not consistent with use of empty as a success return as you've suggested in a few places.
Empty is supposed to have the severest, most abnormal default actions.
For result<T>, it's basically a castrated outcome<T> used only for performance sensitive code, or rather, to say "this function takes performance very seriously".
From the docs and discussion thus far, this is not the impression I had.
The impression I had was that result<T> should be used everywhere in inner exception-enabled code by preference (since exceptions should not be caught, they should be propagated outwards).
That's one approach. But definitely not the sole one. result<T> may also be returned from any function which solely calls STL exception throwing code. Indeed, Outcome provides a catch-to-result macro just for that. This lets you call STL code easily from within noexcept-land.
And then up at the entrypoint from the sea of noexcept to the exception-enabled island, that entrypoint (which is itself noexcept) should return an outcome<> from the result<> of its children or an internal try-catch to propagate out an exception_ptr.
This part is often the case.
outcome<> is used almost exclusively within the sea; result<> only rarely (for cases where an exception cannot occur).
Not necessarily. In my own code to date, I've mostly used outcome<> in exception throwing land and result<> in noexcept-land. But you're right that in most code bases, you probably would use outcome<T> for that yes.
Further up the call stack (back in exception-throwing land) you receive an outcome<> and immediately unpack and rethrow any exception, or otherwise go back to returning result<>s.
Yes, but don't forget result<T>.value() throws the exact same exception as outcome<T>(result<T>).value(). Certainly when using AFIO v2, I find myself writing a lot of afio_function().value();. I really wish it were more permissible to throw from destructors, then if the return were errored and you don't deal with it, it throws, no .value() needed. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

On 2/06/2017 06:33, Niall Douglas wrote:
On 01/06/2017 00:35, Gavin Lambert wrote:
On 1/06/2017 08:17, Niall Douglas wrote:
The programmer, when working with outcome<T>, knows that calling .exception() will return the exact same exception as would be thrown when calling .value(), thus saving wrapping .value() in a try...catch just to capture the exception_ptr emitted.
Except empty. value() throws that as an exception. exception() also throws that as an exception (not returning it), which means that the programmer can't make that assumption you just claimed.
Maybe that's what you want (as it leads very quickly to empty == std::terminate), but it's not consistent with use of empty as a success return as you've suggested in a few places.
Empty is supposed to have the severest, most abnormal default actions.
But it means that in a noexcept method, it's unsafe to call exception() without a try-catch, even though it would be safe to call in any case other than empty. And it means that exception() doesn't return the exception that would be thrown by value() in that case either. Maybe the answer is "so check for empty first!" but that seems like clutter if you're not expecting to see empty but still need to put it in to defensively avoid a std::terminate. And it's bizarre to have empty have super-throw behaviour if the method wants to treat it like an optional success value, as you've suggested is occasionally useful. Why not just have exception() return the empty-state exception that value() would have thrown?
Certainly when using AFIO v2, I find myself writing a lot of afio_function().value();. I really wish it were more permissible to throw from destructors, then if the return were errored and you don't deal with it, it throws, no .value() needed.
It's legal; you just need to explicitly declare it as noexcept(false) and only throw the exception if (!std::uncaught_exception()). Though people will probably still yell at you about it. That sort of policy can sometimes bite you with copyable types, though. Sure, you could mark the source of a copy as "handled", but you don't really know which one is going to be kept.

2017-06-02 2:06 GMT+02:00 Gavin Lambert via Boost <boost@lists.boost.org>:
On 2/06/2017 06:33, Niall Douglas wrote:
On 01/06/2017 00:35, Gavin Lambert wrote:
On 1/06/2017 08:17, Niall Douglas wrote:
The programmer, when working with outcome<T>, knows that calling .exception() will return the exact same exception as would be thrown when calling .value(), thus saving wrapping .value() in a try...catch just to capture the exception_ptr emitted.
Except empty. value() throws that as an exception. exception() also throws that as an exception (not returning it), which means that the programmer can't make that assumption you just claimed.
Maybe that's what you want (as it leads very quickly to empty == std::terminate), but it's not consistent with use of empty as a success return as you've suggested in a few places.
Empty is supposed to have the severest, most abnormal default actions.
But it means that in a noexcept method, it's unsafe to call exception() without a try-catch, even though it would be safe to call in any case other than empty. And it means that exception() doesn't return the exception that would be thrown by value() in that case either.
Maybe the answer is "so check for empty first!" but that seems like clutter if you're not expecting to see empty but still need to put it in to defensively avoid a std::terminate.
There is a reasonable mental model behind it. But you have to forget the other example (where Niall is using `result<T>` as though it were a `boost::optional<T>`). Here is the model: a function that produces `outcome<T>` is allowed to use the empty state *temporarily*, but is required to put it to either `has_value` or `has_exception` (or `has_error`) before it returns the final value. Otherwise it is probably a bug. The consumer function that obtains the `outcome<T>` can assume that it is not this empty state. This is similar situation to when you are obtaining a `std::shared_ptr` or a `std::unique_ptr` from some function. You often do not have to put an if-statement to check if it is nullptr. You trust that if someone decided to build a shared_ptr, this is in order to actually have some object with shared semantics.
And it's bizarre to have empty have super-throw behaviour if the method wants to treat it like an optional success value, as you've suggested is occasionally useful.
Why not just have exception() return the empty-state exception that value() would have thrown?
Certainly when using AFIO v2, I find myself writing a lot of
afio_function().value();. I really wish it were more permissible to throw from destructors, then if the return were errored and you don't deal with it, it throws, no .value() needed.
It's legal; you just need to explicitly declare it as noexcept(false) and only throw the exception if (!std::uncaught_exception()). Though people will probably still yell at you about it.
I would protest for one. You either throw from destructor of you don't throw from destructor. But I do not accept that you sometimes throw and you sometimes don't. But there is a more fundamental objection to it. Unless you use destructors to perform significant tasks and business logic, the normal usage of C++ classes is that other member function do the business logic, and destructors only clean up resources. If you failed to do the business logic you start the stack unwinding because other people up the stack depend on your having finished your part of the logic. In contrast, when you have failed to release resources, people up the stack still consider your business task as finished and they can work with it, and likely will not use the resource you are about to waste (and even if they need it, they will be informed by a throw from a constructor of other resource-handling class). Regards, &rzej;

On 2/06/2017 18:45, Andrzej Krzemienski wrote:
There is a reasonable mental model behind it. But you have to forget the other example (where Niall is using `result<T>` as though it were a `boost::optional<T>`).
It does make more sense that way around.
I would protest for one. You either throw from destructor of you don't throw from destructor. But I do not accept that you sometimes throw and you sometimes don't.
The purpose of avoiding the throw when std::uncaught_exception() is true is to avoid confusing everyone with a validation error when you're already in the middle of an unwind (and thus the thing you were validating is probably uninteresting now). This also avoids clobbering the original exception with your new one, which makes life better for everyone.
But there is a more fundamental objection to it. Unless you use destructors to perform significant tasks and business logic, the normal usage of C++ classes is that other member function do the business logic, and destructors only clean up resources. If you failed to do the business logic you start the stack unwinding because other people up the stack depend on your having finished your part of the logic. In contrast, when you have failed to release resources, people up the stack still consider your business task as finished and they can work with it, and likely will not use the resource you are about to waste (and even if they need it, they will be informed by a throw from a constructor of other resource-handling class).
While I agree in general, there are occasionally some interesting benefits from abusing destructors. One such I've seen in Google Test, where you can add arbitrary data to any assertion just by streaming it, eg: EXPECT_TRUE(foo(bar)) << "foo did something weird" << bar << baz; The way this works is to collect up the items into a temporary object which then assembles into a string and carries out the actual action in the destructor, which obviously can throw for several reasons. Abuse? Yes. But it enables a very nice coding syntax.

2017-06-02 8:57 GMT+02:00 Gavin Lambert via Boost <boost@lists.boost.org>:
On 2/06/2017 18:45, Andrzej Krzemienski wrote:
There is a reasonable mental model behind it. But you have to forget the other example (where Niall is using `result<T>` as though it were a `boost::optional<T>`).
It does make more sense that way around.
I would protest for one. You either throw from destructor of you don't
throw from destructor. But I do not accept that you sometimes throw and you sometimes don't.
The purpose of avoiding the throw when std::uncaught_exception() is true is to avoid confusing everyone with a validation error when you're already in the middle of an unwind (and thus the thing you were validating is probably uninteresting now). This also avoids clobbering the original exception with your new one, which makes life better for everyone.
But there is a more fundamental objection to it. Unless you use destructors
to perform significant tasks and business logic, the normal usage of C++ classes is that other member function do the business logic, and destructors only clean up resources. If you failed to do the business logic you start the stack unwinding because other people up the stack depend on your having finished your part of the logic. In contrast, when you have failed to release resources, people up the stack still consider your business task as finished and they can work with it, and likely will not use the resource you are about to waste (and even if they need it, they will be informed by a throw from a constructor of other resource-handling class).
While I agree in general, there are occasionally some interesting benefits from abusing destructors. One such I've seen in Google Test, where you can add arbitrary data to any assertion just by streaming it, eg:
EXPECT_TRUE(foo(bar)) << "foo did something weird" << bar << baz;
The way this works is to collect up the items into a temporary object which then assembles into a string and carries out the actual action in the destructor, which obviously can throw for several reasons.
Abuse? Yes. But it enables a very nice coding syntax.
I do not mind it. You do not start evaluating your assertion when the stack is being unwound anyway. But I would expect std::terminate if evaluating `bar` throws and then the destructor also throws. I mean both "I accept it" and "I prefer it". Regards, &rzej;

On 02/06/2017 01:06, Gavin Lambert via Boost wrote:
On 2/06/2017 06:33, Niall Douglas wrote:
On 01/06/2017 00:35, Gavin Lambert wrote:
On 1/06/2017 08:17, Niall Douglas wrote:
The programmer, when working with outcome<T>, knows that calling .exception() will return the exact same exception as would be thrown when calling .value(), thus saving wrapping .value() in a try...catch just to capture the exception_ptr emitted.
Except empty. value() throws that as an exception. exception() also throws that as an exception (not returning it), which means that the programmer can't make that assumption you just claimed.
Maybe that's what you want (as it leads very quickly to empty == std::terminate), but it's not consistent with use of empty as a success return as you've suggested in a few places.
Empty is supposed to have the severest, most abnormal default actions.
But it means that in a noexcept method, it's unsafe to call exception() without a try-catch, even though it would be safe to call in any case other than empty. And it means that exception() doesn't return the exception that would be thrown by value() in that case either.
That was intentional. I in fact strongly toyed with attempt to access empty being an immediate std::terminate(), but I decided that throwing a special unique exception type would cause std::terminate() anyway in noexcept code, whilst in exception throwing land it was probably better not the arbitrarily exit the process. I could be very easily persuaded to put it back to std::terminate though.
Maybe the answer is "so check for empty first!" but that seems like clutter if you're not expecting to see empty but still need to put it in to defensively avoid a std::terminate.
And it's bizarre to have empty have super-throw behaviour if the method wants to treat it like an optional success value, as you've suggested is occasionally useful.
The programmer can always override the default handling of empty, value, error, exception. This is by design.
Why not just have exception() return the empty-state exception that value() would have thrown?
Hopefully now explained above. Would you prefer a std::terminate on accessing empty instead? I'm strongly leaning towards that now we'll have split empty-capable vs non-empty-capable types, indeed I just logged that to https://github.com/ned14/boost.outcome/issues/54. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

On 2/06/2017 22:23, Niall Douglas wrote:
Hopefully now explained above. Would you prefer a std::terminate on accessing empty instead? I'm strongly leaning towards that now we'll have split empty-capable vs non-empty-capable types, indeed I just logged that to https://github.com/ned14/boost.outcome/issues/54.
If a non-empty-capable type gets into a state where it can't be anything but empty, then a std::terminate seems justified. Otherwise I'm dubious -- if you're explicitly using an empty-capable type, then is empty really a serious error any more? But then, as I've said before, I'm biased against std::terminate in general, so perhaps I'm not the best one to ask.

2017-05-31 22:17 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
On 31/05/2017 14:59, Andrzej Krzemienski via Boost wrote:
2017-05-31 15:45 GMT+02:00 Niall Douglas via Boost < boost@lists.boost.org>: Errors -- usual failure, Exceptions -- exceptional failure. Ok. But when I call `error()` it is like saying "tell me if a *usual* failure ocurred", I get no exception, fine. When I call `exception()` it is like saying "tell me if an *exceptional* failure ocurred", and you are giving me a non-exceptional one.
This does not (yet) sound consistent to me.
It makes sense when one assumes that outcome<T> accumulates result<T>, which it does. Outcome's design assumes that all outcome types eventually end up in an outcome<T>.
The programmer, when working with outcome<T>, knows that calling .exception() will return the exact same exception as would be thrown when calling .value(), thus saving wrapping .value() in a try...catch just to capture the exception_ptr emitted.
Ok, this is important. And not obvious from the docs. But I do not think it is true in the face of the potential empty state.
The actually odd one out here is .error(), as Emil correctly spotted. It is there basically for the programmer to more efficiently intercept when an error code to exception throw conversion would occur, which is expensive.
So, for outcome<T>, start with assuming that .value() and .exception() fully represent the twin sides of behaviour, and .error() is an early out mechanism.
For result<T>, it's basically a castrated outcome<T> used only for performance sensitive code, or rather, to say "this function takes performance very seriously".
Is it also true that when I call `exception()` it will give me the error_code_extended wrapped into an exception_ptr, if there is one?
Does this make more sense?
This is my current understanding: When inspecting `outcome`, I should use `exception()` to get information about any "failure" (either reported by exception_ptr or by error_code_extended). Function `error()` is either for performance or to discriminate between exception_ptr or by error_code_extended. Did I get it right? (I hope so, and note that in order to describe that one does not have to say that "some failures are expected, and others are not expected") Regards, &rzej;

On Wed, May 31, 2017 at 6:45 AM, Niall Douglas via Boost < boost@lists.boost.org> wrote:
On 31/05/2017 14:25, Andrzej Krzemienski via Boost wrote:
2017-05-31 15:17 GMT+02:00 Niall Douglas via Boost < boost@lists.boost.org>:
If you need a motivating example, imagine a user accumulates result<T>'s from other code it calls into outcome<T>'s, and then does .exception() on the outcome<T>'s.
Yes, I can understand why one wants to treat error_code and exception_ptr uniformly. But recognizing an error_code in `exception()` and not recognizing an exception_ptr in `error()` looks quite arbitrary to me: not guided ba an intuitive mental model.
Not at all.
Errors are not exceptional. They are expected failure.
Exceptions are exceptional. They are **un**expected failure.
By which definition is that true? When I call a function which may throw, that is not unexpected at all. The calling code is specifically designed to deal with that possibility. I see the difference between a C function returning ERROR_OPEN_FAILED and a C++ function throwing open_failed_errror() as purely mechanical. Proceeding to read the file when ERROR_OPEN_FAILED was returned is an error, and 99 times out of 100 you need an if to prevent control from reaching that code. In the open_failed_error() case the compiler writes the if for you, that's about it the only difference.

On 31/05/2017 18:05, Emil Dotchevski via Boost wrote:
On Wed, May 31, 2017 at 6:45 AM, Niall Douglas via Boost <
Errors are not exceptional. They are expected failure.
Exceptions are exceptional. They are **un**expected failure.
By which definition is that true? When I call a function which may throw, that is not unexpected at all. The calling code is specifically designed to deal with that possibility.
That maxim "exceptions are **exceptional**" is a very, very common one in C++. You'll find it said all over stackoverflow and many other sources.
I see the difference between a C function returning ERROR_OPEN_FAILED and a C++ function throwing open_failed_errror() as purely mechanical. Proceeding to read the file when ERROR_OPEN_FAILED was returned is an error, and 99 times out of 100 you need an if to prevent control from reaching that code. In the open_failed_error() case the compiler writes the if for you, that's about it the only difference.
Both the Filesystem TS and the Networking TS are designed around only throwing exceptions in extremely unusual situations. Most failure is treated as likely, and returned via error_code. Outcome copies exactly the same model and philosophy. Errors are ordinary (handled at the point of occurrence), exceptions are exceptional (abort what we are doing). By uplifting an error into an exception, one is basically saying "I can't handle this ordinary failure here, please abort everything". If that sounds very similar to the Rust and Swift error handling model, that is because it is. Exception throws are for aborting only. Not control flow. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-31 22:42 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
On 31/05/2017 18:05, Emil Dotchevski via Boost wrote:
On Wed, May 31, 2017 at 6:45 AM, Niall Douglas via Boost <
Errors are not exceptional. They are expected failure.
Exceptions are exceptional. They are **un**expected failure.
By which definition is that true? When I call a function which may throw, that is not unexpected at all. The calling code is specifically designed to deal with that possibility.
That maxim "exceptions are **exceptional**" is a very, very common one in C++. You'll find it said all over stackoverflow and many other sources.
I see the difference between a C function returning ERROR_OPEN_FAILED and a C++ function throwing open_failed_errror() as purely mechanical. Proceeding to read the file when ERROR_OPEN_FAILED was returned is an error, and 99 times out of 100 you need an if to prevent control from reaching that code. In the open_failed_error() case the compiler writes the if for you, that's about it the only difference.
Both the Filesystem TS and the Networking TS are designed around only throwing exceptions in extremely unusual situations.
Most failure is treated as likely, and returned via error_code.
Outcome copies exactly the same model and philosophy. Errors are ordinary (handled at the point of occurrence), exceptions are exceptional (abort what we are doing). By uplifting an error into an exception, one is basically saying "I can't handle this ordinary failure here, please abort everything".
If that sounds very similar to the Rust and Swift error handling model, that is because it is. Exception throws are for aborting only. Not control flow.
I recommend that we drop terms "exceptional" and "expected". And instead say "error conditions we chose to treat with explicit control flows" and "error conditions we prefer to treat with implicit control flows". Regards, &rzej;

On 31/05/2017 01:18, Niall Douglas wrote:
FWIW, my preferred options would be (which I think were the originals):
- value() throws if holding error or exception (or empty)
- error() returns E{} if holding value or E{errc::has_exception} if holding exception, or E{errc::no_value} if empty (names made up on the spot, doesn't matter)
The current behaviour is "always throw on observing empty". That's what makes empty special (and it traps unintentional propagation of empty).
That makes sense when empty is always "you forgot to return a value". However you've said yourself that sometimes you've hijacked it as an intentional return of no value (as in optional<T>), and that's where things get murkier.
- exception() returns nullptr if holding value or error or empty
The current behaviour is to return null exception_ptr if valued (so if(eptr) would be false i.e. "no exception here"), std::make_exception_ptr(std::system_error(error())) if errored, throw on empty.
So an errored state is also an excepted state, but an excepted state is never an errored state. has_exception() returns true for either errored or excepted states.
std::make_exception_ptr(std::system_error(error())) is fairly cheap, a few thousand CPU cycles. std::exception_ptr's are heavy anyway.
I can understand that, but I'm not sure I can entirely agree with it. As previously mentioned an error_code return is supposed to indicate a non-exceptional error, while an exception is exceptional. So this feels like promoting a non-serious error to a serious error. Having said that, it's not a deal-breaker; both behaviours make sense in their own way, and that behaviour could be convenient in some cases. Although... should it actually throw on empty or just return an exception_ptr containing an exception that indicates it was unexpectedly empty? The only place you should be using outcome<> rather than result<> is inside noexcept methods, so throwing is fairly serious.

On 31/05/2017 00:43, Gavin Lambert via Boost wrote:
On 31/05/2017 01:18, Niall Douglas wrote:
The current behaviour is "always throw on observing empty". That's what makes empty special (and it traps unintentional propagation of empty).
That makes sense when empty is always "you forgot to return a value". However you've said yourself that sometimes you've hijacked it as an intentional return of no value (as in optional<T>), and that's where things get murkier.
You do realise there is nothing stopping a person using the valued state to return an error, the errored state to return a value, and the excepted state to return a type erased smart pointer? Empty state is no different.
- exception() returns nullptr if holding value or error or empty
The current behaviour is to return null exception_ptr if valued (so if(eptr) would be false i.e. "no exception here"), std::make_exception_ptr(std::system_error(error())) if errored, throw on empty.
So an errored state is also an excepted state, but an excepted state is never an errored state. has_exception() returns true for either errored or excepted states.
std::make_exception_ptr(std::system_error(error())) is fairly cheap, a few thousand CPU cycles. std::exception_ptr's are heavy anyway.
I can understand that, but I'm not sure I can entirely agree with it. As previously mentioned an error_code return is supposed to indicate a non-exceptional error, while an exception is exceptional. So this feels like promoting a non-serious error to a serious error.
Sure. But this semantic also means that errors don't get accidentally lost if the programmer checks for an exception without checking for an error. I figured that to be a safer default.
Having said that, it's not a deal-breaker; both behaviours make sense in their own way, and that behaviour could be convenient in some cases.
Although... should it actually throw on empty or just return an exception_ptr containing an exception that indicates it was unexpectedly empty? The only place you should be using outcome<> rather than result<> is inside noexcept methods, so throwing is fairly serious.
In my own code to date my majority usage of outcome<> is in throwing code. I use it to "collect" try...catch islands, outside of the island writing exception safe code becomes considerably easier. I made empty the odd man out, so it always throws, consistently. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-31 14:43 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
On 31/05/2017 00:43, Gavin Lambert via Boost wrote:
On 31/05/2017 01:18, Niall Douglas wrote:
The current behaviour is "always throw on observing empty". That's what makes empty special (and it traps unintentional propagation of empty).
That makes sense when empty is always "you forgot to return a value". However you've said yourself that sometimes you've hijacked it as an intentional return of no value (as in optional<T>), and that's where things get murkier.
You do realise there is nothing stopping a person using the valued state to return an error, the errored state to return a value, and the excepted state to return a type erased smart pointer?
That is of course true, but no-one has ever brought such obviously invalid (or at least confusing/hackish) use cases.
Empty state is no different.
The difference is that you have brought it up as an example of where the empty state could be used in practice. In the code written by the author of Boost.Outcome. This is easily taken as usage recommendation. This is the message I got form reading your replies: It is easy to implement and use `outcome` without an empty state in the interface, but without empty state, the use case from AFIO would not work. This is about the only use case you have shown us. I am not saying that you have said that explicitly. This is simply my impression after reading all the posts that mentioned the subject.
- exception() returns nullptr if holding value or error or empty
The current behaviour is to return null exception_ptr if valued (so if(eptr) would be false i.e. "no exception here"), std::make_exception_ptr(std::system_error(error())) if errored, throw on empty.
So an errored state is also an excepted state, but an excepted state is never an errored state. has_exception() returns true for either errored or excepted states.
std::make_exception_ptr(std::system_error(error())) is fairly cheap, a few thousand CPU cycles. std::exception_ptr's are heavy anyway.
I can understand that, but I'm not sure I can entirely agree with it. As previously mentioned an error_code return is supposed to indicate a non-exceptional error, while an exception is exceptional. So this feels like promoting a non-serious error to a serious error.
Sure. But this semantic also means that errors don't get accidentally lost if the programmer checks for an exception without checking for an error. I figured that to be a safer default.
So this is to prevent accidental user bugs? Or rather the consequence of the mental model where "error" means either error_code or exception_ptr? Regards, &rzej;

On 31/05/2017 14:11, Andrzej Krzemienski via Boost wrote:
2017-05-31 14:43 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
You do realise there is nothing stopping a person using the valued state to return an error, the errored state to return a value, and the excepted state to return a type erased smart pointer?
Empty state is no different.
The difference is that you have brought it up as an example of where the empty state could be used in practice. In the code written by the author of Boost.Outcome. This is easily taken as usage recommendation.
Use *case* recommendation.
This is the message I got form reading your replies: It is easy to implement and use `outcome` without an empty state in the interface, but without empty state, the use case from AFIO would not work. This is about the only use case you have shown us.
You must have missed my preceding example where we initialise a result<T> to empty before entering a loop which may call a worker function depending on predicates, accumulating its returned result<T> into the stacked result<T>. On loop exit, we test the stacked result<T> for emptiness to see if we ever called the worker function (i.e. satisfied the predicates at least once). This use pattern saves on a separate "bool found = false;" Most usage of result<T> in AFIO never uses the empty state, in fact, I think it actually the case that AFIO *never* intentionally uses the empty state internally nor externally. It's what attracts me to a non-empty-capable result<T> type. KernelTest does make substantial use of the empty state though.
I can understand that, but I'm not sure I can entirely agree with it. As previously mentioned an error_code return is supposed to indicate a non-exceptional error, while an exception is exceptional. So this feels like promoting a non-serious error to a serious error.
Sure. But this semantic also means that errors don't get accidentally lost if the programmer checks for an exception without checking for an error. I figured that to be a safer default.
So this is to prevent accidental user bugs? Or rather the consequence of the mental model where "error" means either error_code or exception_ptr?
Let me repeat: **exceptional** means either error_code or exception_ptr. **error** means only error_code. You can think of it like this: failure to explicitly check for an error results in an exceptional circumstance. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

2017-05-30 1:41 GMT+02:00 Gavin Lambert via Boost <boost@lists.boost.org>:
On 30/05/2017 11:25, Vicente J. Botet Escriba wrote:
Le 29/05/2017 à 17:05, Niall Douglas a écrit :
- error_type& .error_raw() - reinterpret_cast<error_type&>
evidently we don't like the _raw suffix.
.error_unsafe(), .unsafe_error(), .unchecked_error() all work for me too.
How likes any of those? Who prefers error() to throw if there is a value? or return by value and return E{} when there is a value and I don't know hat when there is an exception_ptr?
FWIW, my preferred options would be (which I think were the originals):
- value() throws if holding error or exception (or empty)
- error() returns E{} if holding value or E{errc::has_exception} if holding exception, or E{errc::no_value} if empty (names made up on the spot, doesn't matter)
- exception() returns nullptr if holding value or error or empty
These seem like safe and reasonable defaults and permits code to always extract an error_code or exception_ptr even on success, which can aid certain interop scenarios with older APIs and otherwise simplify caller logic.
I more-less agree with this position. I just with you didn't use word "safe". It carries emotional rather than technical value.
I would prefer that unchecked_* (or whatever) versions did not exist as then nobody would accidentally call them and perhaps introduce UB. But I'm not strongly opposed to them.
If you have somebdy who *accidentally* calls member functions in the program, using "checked" versions is not going to help much. Regards, &rzej;

2017-05-30 2:54 GMT+02:00 Peter Dimov via Boost <boost@lists.boost.org>:
Vicente J. Botet Escriba wrote:
Who prefers error() to throw if there is a value?
I do not have a strong opinion. If I will ever call it, I will make sure that the error code is there, so whatever you choose, will work for me. But just to remind everyone. We are not talking about an ordinaty throw here. It will be something like BOOST_THROW_EXCEPTION: it will throw on platforms with execeptions. On others it may abort().
I do.
I honestly don't understand the affection for undefined behavior. Not in principle, and not in this specific case.
People who argue in favour of narrow contracts want narrow contracts -- not UB. A narrow contract tells under what circumstances is it correct to call a given function. Narrow contracts in library interface do not introduce UB. Library users introduce UB when they use a library incorrectly. You cannot prevent users form using a library incorrectly. You may try to work around their incorrect usages, but it is not obviously superior a solution. Regards, &rzej;

On 30/05/2017 00:25, Vicente J. Botet Escriba wrote:
Le 29/05/2017 à 17:05, Niall Douglas via Boost a écrit :
I can not ignore this Outcome review, but I have not see too much people saying that the semantic you have done to this functions is the good one. Strange. I've been surprised at how little criticism there has been of my design choices. Silence can be interpreted both ways I guess.
he he, you find that your library has not been criticized?
People started off with a lot of problems with my design choices for Outcomes. But as discussion proceeded and people thought through the balance of factors, approximately half ended up deciding that my original design was well balanced, including the formal empty state. Of the remaining other half, many wanted narrow observers with __builtin_unreachable() being used to enforce static correctness checking at compile time. I can see value to that, so I'll be adding varieties implementing those with implicit conversion on demand to the runtime checked varieties. The only other design criticism I've noticed is that APIs returning Outcomes are not indicating via the type returned whether the empty state is a valid return or not. I think that important, so I will provide varieties to represent that situation too. The common underlying storage has not been criticised apart from by yourself Vicente, nor the design of "API personalities" sitting on top of the common storage (again, apart from yourself). People aren't happy with the profusion of varieties thinking that a single object design should rule them all as is the Boost way, but I haven't got the sense it's a showstopper for a future acceptance. My argument that it's the same object, just wearing a different face depending on what variety the programmer has selected, appears to have been accepted as viable, if non-ideal in each individual's opinion.
I do agree with this philosophy, but if you're going to go narrow, you need to go narrow on everything including .value(). You need to consistently choose one or the other. No middle ground.
value() id wide operator*() is narrow.
Why? Just because optional made that mistake? If narrow is good, make the entire thing consistently narrow. I have no objection to all member functions all being narrow. The programmer, knowing that it is a narrow-only type, can not accidentally write the wrong thing. It helps code auditing to be consistent and simple. If you want to mix narrow and wide observers into the same object, I cannot abide by making less programmer typing choose the narrow UB observers. It is bad design, and I think it will cause programmers to inadvertently write UB code out of laziness. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Niall Douglas wrote:
value() id wide operator*() is narrow.
Why? Just because optional made that mistake?
The motivation here is that in if( o ) { o->this_(); o->that(); o->this_other_thing(); o->that_too(); o->almost_forgot(); } there is needless replication of the check. This, as I already mentioned, stems from a desire to operate on the value inside 'o' directly, which is not my preferred style in result's case, where I'd prefer to extract it instead. There is a middle ground here, which I was pondering for a while, make has_value return T* instead of bool. T* has_value(); T const* has_value() const; Returns: a pointer to the contained value if one is present, otherwise nullptr Now the above sequence is spelled if( auto* p = o.has_value() ) { p->this_(); p->and_so_on(); } Not sure I particularly like that, but for people who like "consistency with X" arguments, this is like variant's get_if, whereas value() is like variant's get(). This still allows the ordinary if( r.has_value() ) check to work as-is, so nothing is lost in principle. But if this has_value signature strikes you as a bit too novel, the more traditional T* value_if() is also an option. Note how it strikes a balance where the function itself has a wide contract, but then if you use the returned pointer without a check you're on your own.

On 30/05/2017 13:25, Peter Dimov via Boost wrote:
Niall Douglas wrote:
value() id wide operator*() is narrow.
Why? Just because optional made that mistake?
The motivation here is that in
if( o ) { o->this_(); o->that(); o->this_other_thing(); o->that_too();
o->almost_forgot(); }
there is needless replication of the check. This, as I already mentioned, stems from a desire to operate on the value inside 'o' directly, which is not my preferred style in result's case, where I'd prefer to extract it instead.
Oh I get that was the motivation, and that's exactly where I find the most objection with Optional: it has some pointer semantics, but it is not a pointer, and so shouldn't be pretending to be one. It not being a pointer should have always been made clear to end users (by forcing them type more boilerplate) to extract immediately or else bind a lvalue ref to the internally stored value. Don't treat not-a-pointer as a pointer. I also, weirdly enough given the discussion till now, really dislike the wide contracts anywhere on Optional. Unlike Expected or Outcome, the compiler can instantly tell if you're being stupid when using Optional. So I'd have chosen all narrow observers personally, and have the compiler error with obvious UB usage and warn if you observe an Optional returned by an extern function without checking its state first. But that ship has sailed sadly.
There is a middle ground here, which I was pondering for a while, make has_value return T* instead of bool.
It's a clever idea, but it reminds me too much of C++ 98 days of returning void * and such to implement boolean testing. We all got badly bitten with those tricks, for example if(std::cout) is not a compile error. *Shudder*. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

On Tue, May 30, 2017 at 3:51 PM, Niall Douglas via Boost < boost@lists.boost.org> wrote:
On 30/05/2017 13:25, Peter Dimov via Boost wrote:
Niall Douglas wrote: There is a middle ground here, which I was pondering for a while, make has_value return T* instead of bool.
It's a clever idea, but it reminds me too much of C++ 98 days of returning void * and such to implement boolean testing. We all got badly bitten with those tricks
But that was mostly from implicit conversions though, while here it's an explicit method call. My concern is more that I expect a has_value() method to return a boolean, not a pointer. value_if() OTOH is elegant, almost self-explanatory, and the if(auto* p = o.value_if()) idiom is quite readable, despite the two ifs. FWIW. --DD

2017-05-30 16:01 GMT+02:00 Dominique Devienne via Boost < boost@lists.boost.org>:
On Tue, May 30, 2017 at 3:51 PM, Niall Douglas via Boost < boost@lists.boost.org> wrote:
On 30/05/2017 13:25, Peter Dimov via Boost wrote:
Niall Douglas wrote: There is a middle ground here, which I was pondering for a while, make has_value return T* instead of bool.
It's a clever idea, but it reminds me too much of C++ 98 days of returning void * and such to implement boolean testing. We all got badly bitten with those tricks
But that was mostly from implicit conversions though, while here it's an explicit method call.
My concern is more that I expect a has_value() method to return a boolean, not a pointer. value_if() OTOH is elegant, almost self-explanatory, and the if(auto* p = o.value_if()) idiom is quite readable, despite the two ifs. FWIW. --DD
Another name for the function could be `try_value`. But I can still see one use case, where it feels strange. I have just created an `expected` containing a value. Now I want to refer to it directly: ``` expected<vector<T>> mahe_vec() { expected<vector<T>> ans {value}; // creates an empty vector in-place vector<T> & vec = ans.value(); // ... } ``` Niall has indicated a number of times that the second defensive if is removed in context like the following: ``` if (ans.has_value()) ans.value(); // second check elided ``` But in my case with `vector` there is no first check, but I am still sure the value is there because I can trace the entire (quite triviall) life-time of the object. Using value_if is possible, but: ``` expected<vector<T>> mahe_vec() { expected<vector<T>> ans {value}; // creates an empty vector in-place vector<T> & vec = *ans->value_if(); // ... } ``` It doesn't indicate what I am doing clearly enough. If I had a narrow-contract accessor, it would read clearer: ``` expected<vector<T>> mahe_vec() { expected<vector<T>> ans {value}; // creates an empty vector in-place vector<T> & vec = ans.just_value(); // ... } ``` Returning a pointer actually only superficially looks like you are protecting yourself from the narrow contract. You just force the caller to use narrow contract function on the pointer. This reminds me that at some point people here in Boost discussed that get<T>() accessor on variant<T, U, V> should return optional<T&>. Regards, &rzej;

Andrzej Krzemienski wrote:
``` expected<vector<T>> mahe_vec() { expected<vector<T>> ans {value}; // creates an empty vector in-place vector<T> & vec = ans.value(); // ... } ``` Niall has indicated a number of times that the second defensive if is removed in context like the following:
``` if (ans.has_value()) ans.value(); // second check elided ```
But in my case with `vector` there is no first check, but I am still sure the value is there because I can trace the entire (quite triviall) life-time of the object.
It's still elided, because the compiler can see that you're doing the equivalent of storing `true` into `has_value_`.

Returning a pointer actually only superficially looks like you are protecting yourself from the narrow contract. You just force the caller to use narrow contract function on the pointer. This reminds me that at some point people here in Boost discussed that get<T>() accessor on variant<T, U, V> should return optional<T&>.
What the C++ 17 standard eventually did was to add a std::get_if<I>(&variant) which returns a pointer to the value if the variant is in that state. std::get<I>(variant) throws an exception if you don't use the right state. I guess, ultimately, the question to be answered here is how much like std::variant<...> should Outcome and/or Expected be? std::variant<...> doesn't implement completely unchecked observers like optional's operator*(), the best you can get is a nulled pointer observer, no silent reinterpret_cast. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Le 31/05/2017 à 12:55, Niall Douglas via Boost a écrit :
Returning a pointer actually only superficially looks like you are protecting yourself from the narrow contract. You just force the caller to use narrow contract function on the pointer. This reminds me that at some point people here in Boost discussed that get<T>() accessor on variant<T, U, V> should return optional<T&>. What the C++ 17 standard eventually did was to add a std::get_if<I>(&variant) which returns a pointer to the value if the variant is in that state. std::get<I>(variant) throws an exception if you don't use the right state. Hrr, I believe that it had a narrow contract. We have holds_alternative and index. When you know this you don't need a wide contract. I'll ask in std-discussion why we don't have a narrow access. Is it because we want to favor visit? I guess, ultimately, the question to be answered here is how much like std::variant<...> should Outcome and/or Expected be? std::variant<...> doesn't implement completely unchecked observers like optional's operator*(), the best you can get is a nulled pointer observer, no silent reinterpret_cast.
When I started the Expected proposal Variant was not yet there. I believe that we could add the following from variant: * visit * index * get_if/value_if * get If we reach to have a SumType [1] we could as well define * sum_type::size and * sum_type::alternative, Vicente [1] https://github.com/viboes/std-make/tree/master/include/experimental/fundamen...

On 31/05/2017 17:42, Vicente J. Botet Escriba wrote:
Le 31/05/2017 à 12:55, Niall Douglas via Boost a écrit :
I guess, ultimately, the question to be answered here is how much like std::variant<...> should Outcome and/or Expected be? std::variant<...> doesn't implement completely unchecked observers like optional's operator*(), the best you can get is a nulled pointer observer, no silent reinterpret_cast.
When I started the Expected proposal Variant was not yet there. I believe that we could add the following from variant: * visit * index * get_if/value_if * get
Apart from the lack of never empty guarantee (which can be worked around), there appears to be no good reason to not use std::variant<T, E> to implement std::expected<T, E>. Well, apart from poor quality of implementation of std::variant sometimes. For example, libstdc++'s for GCC 7.1 appears to always use type erased memory allocation. Which, frankly, is daft in C++ 14 when you are storing either an int or a std::error_code. I would also assume that std::bad_alloc can be thrown from libstdc++'s std::variant<> :( (Dinkumware's std::variant<...> is really nice, optimises beautifully with an all-trivial-when-possible implementation, well done on them) Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

On Wed, May 31, 2017 at 4:34 PM, Niall Douglas via Boost <boost@lists.boost.org> wrote:
Well, apart from poor quality of implementation of std::variant sometimes. For example, libstdc++'s for GCC 7.1 appears to always use type erased memory allocation. Which, frankly, is daft in C++ 14 when you are storing either an int or a std::error_code. I would also assume that std::bad_alloc can be thrown from libstdc++'s std::variant<> :(
{{citation needed}}. libstdc++'s <variant> may have problems, but dynamic allocation (which would be completely nonconforming) is not one of them.

Well, apart from poor quality of implementation of std::variant sometimes. For example, libstdc++'s for GCC 7.1 appears to always use type erased memory allocation. Which, frankly, is daft in C++ 14 when you are storing either an int or a std::error_code. I would also assume that std::bad_alloc can be thrown from libstdc++'s std::variant<> :(
{{citation needed}}.
See what you make of my toy std::variant<> based Outcome at: https://tinyurl.com/ybxwlewh
libstdc++'s <variant> may have problems, but dynamic allocation (which would be completely nonconforming) is not one of them.
I think I was wrong about the dynamic memory allocation maybe, but something very odd is being done with type erasure. libstdc++'s variant certainly uses a vtable which Dinkumware's does not. Operator=() vectors via that vtable, and that wrecks code folding by the optimiser such that the main() function fully expands into all the checks of state every single operation with potential exception throws, rather than being pruned into a minimum set of assembler. Contrast that libstdc++ spew with what Dinkumware STL outputs which is short enough to paste here: int main(void) { 00007FF7DEED2200 sub rsp,88h 00007FF7DEED2207 mov qword ptr [rsp+20h],0FFFFFFFFFFFFFFFEh { static_checked_result<int> a(5), b(6); std::cout << a.value() << std::endl; 00007FF7DEED2210 mov edx,5 00007FF7DEED2215 mov rcx,qword ptr [__imp_std::cout (07FF7DEED4118h)] 00007FF7DEED221C call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF7DEED40D0h)] 00007FF7DEED2222 mov rcx,rax 00007FF7DEED2225 call std::endl<char,std::char_traits<char> > (07FF7DEED1680h) a = b; std::cout << a.value() << std::endl; 00007FF7DEED222A mov edx,6 00007FF7DEED222F mov rcx,qword ptr [__imp_std::cout (07FF7DEED4118h)] 00007FF7DEED2236 call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF7DEED40D0h)] 00007FF7DEED223C mov rcx,rax 00007FF7DEED223F call std::endl<char,std::char_traits<char> > (07FF7DEED1680h) static_checked_result<int> c(std::make_error_code(std::errc::timed_out)); 00007FF7DEED2244 call std::_Immortalize<std::_Generic_error_category> (07FF7DEED15F0h) 00007FF7DEED2249 nop // std::cout << c.value() << std::endl; // segfaults, as is correct } try { runtime_checked_result<int> a(5), b(6); std::cout << a.value() << std::endl; 00007FF7DEED224A mov edx,5 00007FF7DEED224F mov rcx,qword ptr [__imp_std::cout (07FF7DEED4118h)] 00007FF7DEED2256 call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF7DEED40D0h)] 00007FF7DEED225C mov rcx,rax 00007FF7DEED225F call std::endl<char,std::char_traits<char> > (07FF7DEED1680h) a = b; std::cout << a.value() << std::endl; 00007FF7DEED2264 mov edx,6 00007FF7DEED2269 mov rcx,qword ptr [__imp_std::cout (07FF7DEED4118h)] 00007FF7DEED2270 call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF7DEED40D0h)] 00007FF7DEED2276 mov rcx,rax 00007FF7DEED2279 call std::endl<char,std::char_traits<char> > (07FF7DEED1680h) runtime_checked_result<int> c(std::make_error_code(std::errc::timed_out)); 00007FF7DEED227E call std::_Immortalize<std::_Generic_error_category> (07FF7DEED15F0h) 00007FF7DEED2283 mov dword ptr [rsp+30h],8Ah 00007FF7DEED228B mov qword ptr [rsp+38h],rax 00007FF7DEED2290 movaps xmm0,xmmword ptr [rsp+30h] 00007FF7DEED2295 movups xmmword ptr [rsp+40h],xmm0 00007FF7DEED229A mov byte ptr [rsp+50h],2 std::cout << c.value() << std::endl; // throws 00007FF7DEED229F movdqa xmmword ptr [rsp+30h],xmm0 00007FF7DEED22A5 lea rdx,[rsp+30h] 00007FF7DEED22AA lea rcx,[rsp+58h] 00007FF7DEED22AF call std::system_error::system_error (07FF7DEED17C0h) 00007FF7DEED22B4 lea rdx,[_TI4?AVsystem_error@std@@ (07FF7DEED5048h)] 00007FF7DEED22BB lea rcx,[rsp+58h] 00007FF7DEED22C0 call _CxxThrowException (07FF7DEED32C0h) 00007FF7DEED22C5 mov edx,dword ptr [0] 00007FF7DEED22CC mov rcx,qword ptr [__imp_std::cout (07FF7DEED4118h)] 00007FF7DEED22D3 call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF7DEED40D0h)] 00007FF7DEED22D9 mov rcx,rax 00007FF7DEED22DC call std::endl<char,std::char_traits<char> > (07FF7DEED1680h) 00007FF7DEED22E1 nop } catch (const std::exception &e) { std::cerr << "Exception thrown: " << e.what() << std::endl; } return 0; 00007FF7DEED22E2 xor eax,eax } catch (const std::exception &e) { std::cerr << "Exception thrown: " << e.what() << std::endl; } return 0; 00007FF7DEED22E4 add rsp,88h 00007FF7DEED22EB ret I reckon Dinkumware's variant implementation to be pretty much optimal, lovely. Nice job Microsoft & Dinkumware! Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Niall Douglas wrote:
See what you make of my toy std::variant<> based Outcome at:
FWIW, Niall, I convinced my variant implementation to work in Compiler Explorer: https://godbolt.org/g/juh4WB so you can try it for a spin. It's never-valueless, ever, so you can dispense with the "safe assign" workarounds.

Niall Douglas wrote:
See what you make of my toy std::variant<> based Outcome at:
FWIW, Niall, I convinced my variant implementation to work in Compiler Explorer:
so you can try it for a spin. It's never-valueless, ever, so you can dispense with the "safe assign" workarounds.
Here's a simplistic result<> based on that: https://godbolt.org/g/EG6Kht g++ 7.1 turns this: #include <iostream> result<int> function() { return 14; } int main() { std::cout << function().value() << std::endl; } into this: function(): mov rax, rdi mov DWORD PTR [rdi], 1 mov DWORD PTR [rdi+8], 14 ret main: sub rsp, 8 mov esi, 14 mov edi, OFFSET FLAT:std::cout call std::basic_ostream<char, std::char_traits<char>
::operator<<(int) mov rdi, rax call std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&) xor eax, eax add rsp, 8 ret _GLOBAL__sub_I__Z8functionv: sub rsp, 8 mov edi, OFFSET FLAT:std::__ioinit call std::ios_base::Init::Init() mov edx, OFFSET FLAT:__dso_handle mov esi, OFFSET FLAT:std::__ioinit mov edi, OFFSET FLAT:std::ios_base::Init::~Init() add rsp, 8 jmp __cxa_atexit

On 01/06/2017 15:31, Peter Dimov via Boost wrote:
Niall Douglas wrote:
See what you make of my toy std::variant<> based Outcome at:
FWIW, Niall, I convinced my variant implementation to work in Compiler Explorer:
so you can try it for a spin. It's never-valueless, ever, so you can dispense with the "safe assign" workarounds.
Cool. I have a lot of other stuff which was shelved to make space for this review, so those needs attending to first. They will take a few weeks. Then I need to find employment. Then I'll be able to return to working on open source stuff, and by which time surely Charley's report on this review will have been published. When do you expect your variant to enter Boost? And how well does it work on VS2017? My not particularly complicated toy variant-based Outcome ICEs VS2017, and that's using the shipped Dinkumware variant. I am hoping VS2017.3 improves things. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Niall Douglas wrote:
And how well does it [your variant] work on VS2017?
I have it working, more or less. https://ci.appveyor.com/project/pdimov/variant2/history
My not particularly complicated toy variant-based Outcome ICEs VS2017, and that's using the shipped Dinkumware variant.
I'm so very familiar with that. :-)

On 31 May 2017 at 21:34, Niall Douglas via Boost wrote:
On 31/05/2017 17:42, Vicente J. Botet Escriba wrote:
Le 31/05/2017 à 12:55, Niall Douglas via Boost a écrit :
I guess, ultimately, the question to be answered here is how much like std::variant<...> should Outcome and/or Expected be? std::variant<...> doesn't implement completely unchecked observers like optional's operator*(), the best you can get is a nulled pointer observer, no silent reinterpret_cast.
When I started the Expected proposal Variant was not yet there. I believe that we could add the following from variant: * visit * index * get_if/value_if * get
Apart from the lack of never empty guarantee (which can be worked around), there appears to be no good reason to not use std::variant<T, E> to implement std::expected<T, E>.
Well, apart from poor quality of implementation of std::variant sometimes. For example, libstdc++'s for GCC 7.1 appears to always use type erased memory allocation. Which, frankly, is daft in C++ 14 when you are storing either an int or a std::error_code. I would also assume that std::bad_alloc can be thrown from libstdc++'s std::variant<> :(
Huh? Where do you see this?

2017-05-30 15:51 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
On 30/05/2017 13:25, Peter Dimov via Boost wrote:
Niall Douglas wrote:
value() id wide operator*() is narrow.
Why? Just because optional made that mistake?
The motivation here is that in
if( o ) { o->this_(); o->that(); o->this_other_thing(); o->that_too();
o->almost_forgot(); }
there is needless replication of the check. This, as I already mentioned, stems from a desire to operate on the value inside 'o' directly, which is not my preferred style in result's case, where I'd prefer to extract it instead.
Oh I get that was the motivation, and that's exactly where I find the most objection with Optional: it has some pointer semantics, but it is not a pointer, and so shouldn't be pretending to be one. It not being a pointer should have always been made clear to end users (by forcing them type more boilerplate) to extract immediately or else bind a lvalue ref to the internally stored value. Don't treat not-a-pointer as a pointer.
Maybe, but at least for optional you have one value that you can "refer" to or not. In case of `expected` it is even harder to defent this, because the E in a way is also a value.
I also, weirdly enough given the discussion till now, really dislike the wide contracts anywhere on Optional. Unlike Expected or Outcome, the compiler can instantly tell if you're being stupid when using Optional. So I'd have chosen all narrow observers personally, and have the compiler error with obvious UB usage and warn if you observe an Optional returned by an extern function without checking its state first. But that ship has sailed sadly.
The primary interface for accessing optional's contained value (operator* and operator->) have narrow contract. Regards, &rzej;

2017-05-30 14:25 GMT+02:00 Peter Dimov via Boost <boost@lists.boost.org>:
Niall Douglas wrote:
value() id wide operator*() is narrow.
Why? Just because optional made that mistake?
The motivation here is that in
if( o ) { o->this_(); o->that(); o->this_other_thing(); o->that_too();
o->almost_forgot(); }
there is needless replication of the check. This, as I already mentioned, stems from a desire to operate on the value inside 'o' directly, which is not my preferred style in result's case, where I'd prefer to extract it instead.
There is a middle ground here, which I was pondering for a while, make has_value return T* instead of bool.
T* has_value(); T const* has_value() const;
Returns: a pointer to the contained value if one is present, otherwise nullptr
Now the above sequence is spelled
if( auto* p = o.has_value() ) { p->this_(); p->and_so_on(); }
Not sure I particularly like that, but for people who like "consistency with X" arguments, this is like variant's get_if, whereas value() is like variant's get().
This still allows the ordinary if( r.has_value() ) check to work as-is, so nothing is lost in principle.
But if this has_value signature strikes you as a bit too novel, the more traditional T* value_if() is also an option. Note how it strikes a balance where the function itself has a wide contract, but then if you use the returned pointer without a check you're on your own.
I like that. Regards, &rzej;
participants (10)
-
Andrzej Krzemienski
-
Dominique Devienne
-
Emil Dotchevski
-
Gavin Lambert
-
Gottlob Frege
-
Jonathan Wakely
-
Niall Douglas
-
Peter Dimov
-
Tim Song
-
Vicente J. Botet Escriba