Boost.Outcome review - First questions

Hi, I've tried to read the tutorial, but it is too long for my taste. I will try it later on :( A minor issue * "As inferred in the example code cases earlier, you can check if an |expected<T, E>| has an expected value via a simple test for boolean true or more explicitly via |.has_value()| before retrieving the value using |.value()|." This is not correct. It is correct for the de-reference operator, but .value() don't needs any check with .has_value(). Instead of some deviation, I would expect a report of all deviations and a rationale explaining why it is better to do this way. # "Types |T| and |E| cannot be constructible into one another. This is a fundamental design choice in basic_monad to significantly reduce compile times so it won't be fixed." * "Note though that |expected<void, void>| is permitted (which is legal in the LEWG proposal)." What are the compile time cost |associated the std::experimental::expected<T,E>? | I don't see what |expected<void, void> could mean. | # |unexpected_type<E>| is implemented as an |expected<void, E>| and it lets the basic_monad machinery do the implicit conversion to some |expected<T, E>| through the less representative to more representative conversion rules. Note that our Expected passes the LEWG Expected test suite just fine, so you probably won't notice this implementation detail in your code. The test of my POC implementation couldn't be complete, so this is not a sign of compatibility. Which operation allow to convert expected<void,E> to expected<T,E> and what is the expected behavior if expected<void,E> has a value? # Our |expected<T, E>| defaults E to |std::error_code| rather than |std::error_condition| (the LEWG proposal fixes this in P0323R2). I don't have a strong opinion on which default would be the best. As you suggested, I have added it to the next revision. We don't implement the ordering and hashing operator overloads due to https://akrzemi1.wordpress.com/2014/12/02/a-gotcha-with-optional/. The fact the LEWG proposal does as currently proposed is a defect (it will be discussed by LEWG with P0323R2 in Toronto). As for optional this could be considered a defect. The question is if we want expected to differen from the optional interface. If we considered it a defect, I don't believe that we should remove comparison, but IMHO we should make the constructor from T explicit. I don't expect the committee to fix this for expected and let the issue for optional. We will see. # We don't implement |make_expected_from_call()| as we think it highly likely to be removed from the next version of the proposal due to it conferring little value. This is possible and the function can be implemented very easily. I can live with and without it. # Our |make_expected()| and |make_unexpected()| functions deviate significantly as we believe the LEWG ones to be defective in various ways. This topic will be discussed in Toronto and reconciliation later is highly likely. Please, could you tell how those are defective so that we can fix them? Otherwise I don't see what we could discuss in Toronto. # Our |value_or()| member functions pass through the reference rather than returning by value (we don't understand why the LEWG proposal does except that |std::optional<T>| does, which itself is a bad design choice and it should be changed). Well, what is the advantage to returning by reference? Rather than |void|, our |value_type| and |error_type| typedefs are set to a descriptive compiler error generating type if the Expected is configured with |void|. If you want to know if no type was configured, use the static constexpr bools at |expected<T, E>::has_value_type| and |expected<T, E>::has_error_type| or use |raw_value_type| and |raw_error_type|. The reason for this deviation is to make metaprogramming using Outcome's transports much easier and less error prone. Sorry. I don't understand what is the issue. Could you elaborate? # Our Expected always defines the default, copy and move constructors even if the the type configured is not capable of it. That means |std::is_copy_constructible| etc returns true when they should return false. The reason why is again to significantly improve compile times by hugely reducing the number of templates which need to be instantiated during routine basic_monad usage, and again this won't be fixed. Instead use the static constexpr bools at: Defining these operation when they can not be implemented means that you cannot use SFINAE on this type. Maybe I'm wrong, but I believe we want to check if a type is constructible, ... using the C++ standard type traits. Next follow the first questions I have mainly from the reference documentation of expected<T,E>. In general I find most of the operation not specified enough. I would like to have something in line with the standard way of specifying an interface. * it is not clear from the reference documentation if expected<void,E> is valid? |* |is expected<T,E> the sum type of T, E and empty? and exception_ptr? |"outcome<T>| can be empty, or hold an instance of a type |T|, or hold an instance of |error_code_extended|, or hold an instance of |std::exception_ptr|. This saves you having to wrap an error code into an exception pointer instance, thus also avoiding a memory allocation." std::experimental::expected requires that its Error type to be no throw constructible, copyable and movable. What are the exception warranties for |error_code_extended? | What are the exception warranties for |outcome<T>||? | * inplace construction What is the difference between: constexpr expected (value_t _) noexcept(std::is_nothrow_default_constructible< value_type >::value) Implicit constructor of a valued monad (default constructed) and constexpr expected (in_place_t, Args &&... args) noexcept(std::is_nothrow_constructible< value_type, Arg, Args... >::value) when there is no Args. IMO the second subsumes the first and so there is no need fro the first overload and the value_t tag. * default constructor constexpr expected () noexcept(is_nothrow_default_constructible) Default constructor. This doesn't say what the default constructor does. * Shouldn't constexpr expected (in_place_t, std::initializer_list< U > l) noexcept(std::is_nothrow_constructible< value_type, std::initializer_list< U >>::value) Implicit constructor from an initializer list. be constexpr explicit expected(in_place_t, initializer_list<U> il, Args&&... args); * What is void_rebound and where it is defined constexpr expected (const void_rebound &v) noexcept(std::is_nothrow_copy_constructible< error_type >::value) Implicit constructor from an identically configured basic_monad<void> by copy. * construction from error type What is wrong with template <class... Args> constexpr explicit expected(unexpect_t, Args&&...); template <class U, class... Args> constexpr explicit expected(unexpect_t, initializer_list<U>, Args&&...); Why do you prefer an implicit conversion. This deviation must be in the rationale of the differences. constexpr expected (const error_type &v) noexcept(std::is_nothrow_copy_constructible< error_type >::value) Implicit constructor from a error_type by copy. constexpr expected (error_type &&v) noexcept(std::is_nothrow_move_constructible< error_type >::value) * raw types It is not clear what is the difference between xxx and raw_xxx typedef value_storage_type::value_type value_type The type potentially held by the monad. typedef implementation_policy::value_type raw_value_type The raw type potentially held by the monad. typedef value_storage_type::error_type error_type The error code potentially held by the monad. typedef implementation_policy::error_type raw_error_type The raw error code potentially held by the monad. typedef value_storage_type::exception_type exception_type The exception ptr potentially held by the monad. typedef implementation_policy::exception_type raw_exception_type The raw exception ptr potentially held by the monad. * What is the "The exception ptr potentially held by the monad"? * what is the sizeof expected <T,E>? * is basic_monad something proposed or an implementation detail? If the first, this should be renamed, if the second this class should not appear in the documentation and the word monad should be banned. * What is expected<Policy> in template<class Policy , typename = typename std::enable_if<std::is_same<typename implementation_policy::value_type, typename Policy::value_type>::value || std::is_void<typename Policy::value_type>::value || std::is_constructible<typename implementation_policy::value_type, typename Policy::value_type>::value>::type, typename = typename std::enable_if<std::is_same<typename implementation_policy::error_type, typename Policy::error_type>::value || std::is_constructible<typename implementation_policy::error_type, typename Policy::error_type>::value>::type, typename = typename std::enable_if<std::is_same<typename implementation_policy::exception_type, typename Policy::exception_type>::value || std::is_constructible<typename implementation_policy::exception_type, typename Policy::exception_type>::value>::type> constexpr expected (expected< Policy > &&o, explicit_conversion_from_different_policy=explicit_conversion_from_different_policy()) Explicit move constructor from a basic_monad with a differing implementation policy. For this constructor to be available, value_type, error_type and exception_type must be identical or constructible. * move constructor/copy constructor The reference documentation doesn't says what this does. * Missing move/copy assignment * bool conversion should be explicit * is_ready has nothing to be with expected. * Shouldn't value_or return by value? otherwise what would be the difference between get_or and value_or? * emplace. Missing overload template <class U, class... Args> void emplace(initializer_list<U>, Args&&...); * swap: What is the meaning of swapping an expected with an option? swap must follow the standard signature. * Missing preconditions on the observers, which make them wide operations. Saying that they return an address makes them wide operations however accessing to the contents of this address will be undefined behavior. I prefer the standard way, defining a narrow function and stating clearly the pre-conditions. * what error_or <https://ned14.github.io/boost.outcome/structboost_1_1outcome_1_1v1__xxx_1_1policy_1_1expected__policy__base.html#ae0882c7d380b88b9011bcc60c59903bd> returns if it contains an exception? * what is the difference between get_error_or and error_or? https://github.com/ned14/boost.outcome/issues/19 * Missing overload for get_unexpected. In addition it doesn't return an unexpected_type. constexpr const E & get_unexpected () const; Best, Vicente

A minor issue
* "As inferred in the example code cases earlier, you can check if an |expected<T, E>| has an expected value via a simple test for boolean true or more explicitly via |.has_value()| before retrieving the value using |.value()|."
This is not correct. It is correct for the de-reference operator, but .value() don't needs any check with .has_value().
If you don't check before using .value(), it may throw bad_expected_access<E>.
Instead of some deviation, I would expect a report of all deviations and a rationale explaining why it is better to do this way.
I believe the list of deviations is complete, apart from the defects you found listed below which I will fix. Explaining why I deviate in detail isn't appropriate for the Outcome docs, I have raised why with you privately at length. Depending on what is decided at Toronto, I may write up remaining concerns into a WG21 paper, but in the end Outcome's Expected will track yours more closely than it has, so whatever you decide I'll do too.
# "Types |T| and |E| cannot be constructible into one another. This is a fundamental design choice in basic_monad to significantly reduce compile times so it won't be fixed."
* "Note though that |expected<void, void>| is permitted (which is legal in the LEWG proposal)."
What are the compile time cost |associated the std::experimental::expected<T,E>?
Outcome doesn't do SFINAE on its main constructors. It relies on simple overloading which is much lower compile time cost.
I don't see what |expected<void, void> could mean.
It's legal in the LEWG Expected proposal. It's essentially a boolean.
# |unexpected_type<E>| is implemented as an |expected<void, E>| and it lets the basic_monad machinery do the implicit conversion to some |expected<T, E>| through the less representative to more representative conversion rules. Note that our Expected passes the LEWG Expected test suite just fine, so you probably won't notice this implementation detail in your code.
The test of my POC implementation couldn't be complete, so this is not a sign of compatibility.
Which operation allow to convert expected<void,E> to expected<T,E> and what is the expected behavior if expected<void,E> has a value?
basic_monad, separate to Expected, implements implicit conversion from any less representative form to any more representative form. So: result<void> => result<T> result<T> => outcome<T> expected<void, X> => expected<T, X> ... and so on. That's why implementing unexpected_type as an alias of expected<void, E> "just works". A current outstanding bug (https://github.com/ned14/boost.outcome/issues/16) is failure to compile: result<void> => result<T> ... when T has no default constructor because a valued void state turns into a default constructed T state. That *should* fail to compile of course, but the as_void() free function also fails to compile that, and it should work because otherwise the BOOST_OUTCOME_TRY() machinery fails to compile when the T of the enclosing function doesn't have a default constructor. It's my next issue to fix, it'll be done soon.
# Our |make_expected()| and |make_unexpected()| functions deviate significantly as we believe the LEWG ones to be defective in various ways. This topic will be discussed in Toronto and reconciliation later is highly likely.
Please, could you tell how those are defective so that we can fix them? Otherwise I don't see what we could discuss in Toronto.
The most important problem by far is the inability to do make_expected<const Foo>(Foo()) => expected<const Foo>. I suppose const volatile also should be supported. I have already raised this with you, you said it will be discussed at Toronto. Whatever you end up doing to make_expected() etc to enable const T and const E, I will replicate in Outcome's Expected. Until then I deviate to allow those. expected<const T, const E> is useful sometimes.
# Our |value_or()| member functions pass through the reference rather than returning by value (we don't understand why the LEWG proposal does except that |std::optional<T>| does, which itself is a bad design choice and it should be changed).
Well, what is the advantage to returning by reference?
You don't cause .value_or() to surprisingly fail to compile when T lacks a move or copy constructor. The reference returning edition does not impose the requirement for T to have a move or copy constructor. This choice for .value_or() interferes less on end users. The fact optional implemented .value_or() with a return by value is a mistake. Expected need not repeat that mistake.
# Our Expected always defines the default, copy and move constructors even if the the type configured is not capable of it. That means |std::is_copy_constructible| etc returns true when they should return false. The reason why is again to significantly improve compile times by hugely reducing the number of templates which need to be instantiated during routine basic_monad usage, and again this won't be fixed. Instead use the static constexpr bools at:
Defining these operation when they can not be implemented means that you cannot use SFINAE on this type. Maybe I'm wrong, but I believe we want to check if a type is constructible, ... using the C++ standard type traits.
Correct. static constexpr bools provide the information instead. If reviewers like it, we may inject correct answers into namespace std for the type traits (this is permitted by the C++ standard).
Next follow the first questions I have mainly from the reference documentation of expected<T,E>.
In general I find most of the operation not specified enough. I would like to have something in line with the standard way of specifying an interface.
Already logged to https://github.com/ned14/boost.outcome/issues/26
* it is not clear from the reference documentation if expected<void,E> is valid?
Do you mean there is no reference in the API docs to an expected<void, E> specialisation? I didn't add one as I figured it was self evident and unsurprising. Also, if we document expected<void, E>, we'd have to also do result<void>, outcome<void>, expected<void, void> etc and to be honest, they all behave exactly as you'd expect a void typed edition would: wherever there is a T, you get T=void. Nothing special.
|* |is expected<T,E> the sum type of T, E and empty? and exception_ptr?
expected<T, E> may only have the state of T or E. The valueless by exception state has been eliminated recently in develop branch.
|"outcome<T>| can be empty, or hold an instance of a type |T|, or hold an instance of |error_code_extended|, or hold an instance of |std::exception_ptr|. This saves you having to wrap an error code into an exception pointer instance, thus also avoiding a memory allocation."
std::experimental::expected requires that its Error type to be no throw constructible, copyable and movable.
Those static asserts are gone from develop branch as we now fully implement the never empty guarantee for Expected.
What are the exception warranties for |error_code_extended?
It never throws, not ever. This is pretty clear in the reference docs. Everything is marked noexcept.
What are the exception warranties for |outcome<T>||?
The tutorial covers those. The reference docs will too when issue #26 is implemented. The noexcept modifiers on most of the member functions are pretty clear however.
* inplace construction
What is the difference between:
constexpr expected (value_t _) noexcept(std::is_nothrow_default_constructible< value_type >::value) Implicit constructor of a valued monad (default constructed)
and
constexpr expected (in_place_t, Args &&... args) noexcept(std::is_nothrow_constructible< value_type, Arg, Args... >::value)
when there is no Args. IMO the second subsumes the first and so there is no need fro the first overload and the value_t tag.
The former has lower compile time load for valued default constructing an instance, and it's the constructor used internally by helper functions etc. The latter is there for emplacement construction for those willing to pay the compile time cost.
* default constructor
constexpr expected () noexcept(is_nothrow_default_constructible) Default constructor.
This doesn't say what the default constructor does.
As per LEWG Expected, T is default constructed. The reference docs will be fixed with issue #26.
* Shouldn't
constexpr expected (in_place_t, std::initializer_list< U > l) noexcept(std::is_nothrow_constructible< value_type, std::initializer_list< U >>::value) Implicit constructor from an initializer list.
be
constexpr explicit expected(in_place_t, initializer_list<U> il, Args&&... args);
Ah yes I had forgotten about that. You are correct, but I left it unfixed to remind me to ask here what would be a suitable unit test for testing an "initializer_list<U> il, Args&&... args" constructor where the Args&&... gets used correctly. I was unsure of the semantics. Also, it's logged to https://github.com/ned14/boost.outcome/issues/27.
* What is void_rebound and where it is defined
It was accidentally omitted from the reference docs. It is "using void_rebound = rebind<void>". Logged to https://github.com/ned14/boost.outcome/issues/28.
* construction from error type
Why do you prefer an implicit conversion. This deviation must be in the rationale of the differences.
The deviations list already explains that types T and E cannot be convertible into one another because we use simple overloading to avoid using SFINAE to keep compile time load low. Code written for LEWG Expected will, unless types T and E are convertible into one another, work as expected.
* raw types
It is not clear what is the difference between xxx and raw_xxx
Logged to https://github.com/ned14/boost.outcome/issues/29
* what is the sizeof expected <T,E>?
It says already at the top of the page. It's max(24, sizeof(R)+8) on 64 bit CPUs.
* What is expected<Policy> in
Looks like a misparse by doxygen. Such a thing doesn't exist. Logged to https://github.com/ned14/boost.outcome/issues/30
* bool conversion should be explicit
Another doxygen bug. Logged to https://github.com/ned14/boost.outcome/issues/31.
* is_ready has nothing to be with expected.
Already removed from develop branch.
* Shouldn't value_or return by value? otherwise what would be the difference between get_or and value_or?
The get_*() family of functions is scheduled to be removed as per peer review feedback by https://github.com/ned14/boost.outcome/issues/24
* emplace. Missing overload
template <class U, class... Args> void emplace(initializer_list<U>, Args&&...);
Great catch! Logged to https://github.com/ned14/boost.outcome/issues/32
* Missing preconditions on the observers, which make them wide operations. Saying that they return an address makes them wide operations however accessing to the contents of this address will be undefined behavior.
I prefer the standard way, defining a narrow function and stating clearly the pre-conditions.
Issue #26 will fix these.
* what error_or <https://ned14.github.io/boost.outcome/structboost_1_1outcome_1_1v1__xxx_1_1policy_1_1expected__policy__base.html#ae0882c7d380b88b9011bcc60c59903bd> returns if it contains an exception?
The or parameter. Errored does not include excepted, but excepted includes errored because an error code is emitted as an exception_ptr to a system_error containing that error code.
* Missing overload for get_unexpected. In addition it doesn't return an unexpected_type.
constexpr const E & get_unexpected () const;
I didn't realise there were rvalue, lvalue, const rvalue and const lvalue overloads. That must be a recent thing. Logged to https://github.com/ned14/boost.outcome/issues/33. The return by E rather than expected<void, E> is by design. Remember we permit implicit construction from E, so this is safe and works well. Again, code written for LEWG Expected should never notice this implementation detail. (the real cause for this deviation is because the policy classes cannot return types still being fabricated, so they cannot return an expected<void, E>. If this deviation ever should become a problem, I can relocate get_unexpected to the main class and enable_if it, or else use a trampoline type. But I believe the existing form replicates the same semantics, it should be fine) Vicente thanks very much for such detailed feedback. It was very valuable. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Le 21/05/2017 à 15:59, Niall Douglas via Boost a écrit :
A minor issue
* "As inferred in the example code cases earlier, you can check if an |expected<T, E>| has an expected value via a simple test for boolean true or more explicitly via |.has_value()| before retrieving the value using |.value()|."
This is not correct. It is correct for the de-reference operator, but .value() don't needs any check with .has_value(). If you don't check before using .value(), it may throw bad_expected_access<E>. So you have to fix the text.
Instead of some deviation, I would expect a report of all deviations and a rationale explaining why it is better to do this way. I believe the list of deviations is complete, apart from the defects you found listed below which I will fix. Explaining why I deviate in detail isn't appropriate for the Outcome docs, I have raised why with you privately at length. Depending on what is decided at Toronto, I may write up remaining concerns into a WG21 paper, but in the end Outcome's Expected will track yours more closely than it has, so whatever you decide I'll do too. My idea is to forward your concerns to Toronto, but for that I need arguments that I expected to find in this section.
# "Types |T| and |E| cannot be constructible into one another. This is a fundamental design choice in basic_monad to significantly reduce compile times so it won't be fixed."
* "Note though that |expected<void, void>| is permitted (which is legal in the LEWG proposal)."
What are the compile time cost |associated the std::experimental::expected<T,E>? Outcome doesn't do SFINAE on its main constructors. It relies on simple overloading which is much lower compile time cost. Maybe it has lower compile time, but it is not correct, isn't it? Would you suggest that std::expect shouldn't do SFINAE?
I don't see what |expected<void, void> could mean. It's legal in the LEWG Expected proposal. It's essentially a boolean. Hugh!
# |unexpected_type<E>| is implemented as an |expected<void, E>| and it lets the basic_monad machinery do the implicit conversion to some |expected<T, E>| through the less representative to more representative conversion rules. Note that our Expected passes the LEWG Expected test suite just fine, so you probably won't notice this implementation detail in your code.
The test of my POC implementation couldn't be complete, so this is not a sign of compatibility.
Which operation allow to convert expected<void,E> to expected<T,E> and what is the expected behavior if expected<void,E> has a value? basic_monad, separate to Expected, implements implicit conversion from any less representative form to any more representative form. So: Could you point me to the documentation?
result<void> => result<T> result<T> => outcome<T> expected<void, X> => expected<T, X>
... and so on.
That's why implementing unexpected_type as an alias of expected<void, E> "just works".
A current outstanding bug (https://github.com/ned14/boost.outcome/issues/16) is failure to compile:
result<void> => result<T>
... when T has no default constructor because a valued void state turns into a default constructed T state. That *should* fail to compile of course, but the as_void() free function also fails to compile that, and it should work because otherwise the BOOST_OUTCOME_TRY() machinery fails to compile when the T of the enclosing function doesn't have a default constructor. It's my next issue to fix, it'll be done soon. But what is the meaning if the result<T> if result<void> has a value?
# Our |make_expected()| and |make_unexpected()| functions deviate significantly as we believe the LEWG ones to be defective in various ways. This topic will be discussed in Toronto and reconciliation later is highly likely.
Please, could you tell how those are defective so that we can fix them? Otherwise I don't see what we could discuss in Toronto. The most important problem by far is the inability to do make_expected<const Foo>(Foo()) => expected<const Foo>. I suppose const volatile also should be supported. I have already raised this with you, you said it will be discussed at Toronto. Yes, it will. This is not a good reason to don't provide what already is in or what we discussed together.
Whatever you end up doing to make_expected() etc to enable const T and const E, I will replicate in Outcome's Expected. Until then I deviate to allow those. expected<const T, const E> is useful sometimes. How you deviation allows it in a better way?
# Our |value_or()| member functions pass through the reference rather than returning by value (we don't understand why the LEWG proposal does except that |std::optional<T>| does, which itself is a bad design choice and it should be changed).
Well, what is the advantage to returning by reference? You don't cause .value_or() to surprisingly fail to compile when T lacks a move or copy constructor. The reference returning edition does not impose the requirement for T to have a move or copy constructor. This choice for .value_or() interferes less on end users. Do you have cases where expected<T,E> has a T that is not move/copy constructible? How can you return it? The fact optional implemented .value_or() with a return by value is a mistake. Expected need not repeat that mistake. Could you point me to the C++ standard std::optional defect? Have you created one?
# Our Expected always defines the default, copy and move constructors even if the the type configured is not capable of it. That means |std::is_copy_constructible| etc returns true when they should return false. The reason why is again to significantly improve compile times by hugely reducing the number of templates which need to be instantiated during routine basic_monad usage, and again this won't be fixed. Instead use the static constexpr bools at:
Defining these operation when they can not be implemented means that you cannot use SFINAE on this type. Maybe I'm wrong, but I believe we want to check if a type is constructible, ... using the C++ standard type traits. Correct. static constexpr bools provide the information instead. If reviewers like it, we may inject correct answers into namespace std for the type traits (this is permitted by the C++ standard). Could you elaborate?
Next follow the first questions I have mainly from the reference documentation of expected<T,E>.
In general I find most of the operation not specified enough. I would like to have something in line with the standard way of specifying an interface. Already logged to https://github.com/ned14/boost.outcome/issues/26
* it is not clear from the reference documentation if expected<void,E> is valid? Do you mean there is no reference in the API docs to an expected<void, E> specialisation? I didn't add one as I figured it was self evident and unsurprising. Also, if we document expected<void, E>, we'd have to also do result<void>, outcome<void>, expected<void, void> etc and to be honest, they all behave exactly as you'd expect a void typed edition would: wherever there is a T, you get T=void. Nothing special. Maybe or maybe not. What is the signature of value_or?
|* |is expected<T,E> the sum type of T, E and empty? and exception_ptr? expected<T, E> may only have the state of T or E. The valueless by exception state has been eliminated recently in develop branch. Is this the case also for outcome/result/option?
|"outcome<T>| can be empty, or hold an instance of a type |T|, or hold an instance of |error_code_extended|, or hold an instance of |std::exception_ptr|. This saves you having to wrap an error code into an exception pointer instance, thus also avoiding a memory allocation."
std::experimental::expected requires that its Error type to be no throw constructible, copyable and movable. Those static asserts are gone from develop branch as we now fully implement the never empty guarantee for Expected. Is there
What are the exception warranties for |error_code_extended? It never throws, not ever.
This is pretty clear in the reference docs. Everything is marked noexcept. I don't see it in https://ned14.github.io/boost.outcome/classboost_1_1outcome_1_1v1__xxx_1_1er... Could you point me where noexcept is?
What are the exception warranties for |outcome<T>||? The tutorial covers those. The reference docs will too when issue #26 is implemented. The noexcept modifiers on most of the member functions are pretty clear however. This is the kind of information we want on the reference documentation. Could you tell us what the issue #26 The default actions for each of .value(), .error(), .exception() etc need to be spelled out in the reference docs <https://github.com/ned14/boost.outcome/issues/26> will add to the docs and how this issue is related? What about the other functions, constructors, assignments ...
* inplace construction
What is the difference between:
constexpr expected (value_t _) noexcept(std::is_nothrow_default_constructible< value_type >::value) Implicit constructor of a valued monad (default constructed)
and
constexpr expected (in_place_t, Args &&... args) noexcept(std::is_nothrow_constructible< value_type, Arg, Args... >::value)
when there is no Args. IMO the second subsumes the first and so there is no need fro the first overload and the value_t tag. The former has lower compile time load for valued default constructing an instance, and it's the constructor used internally by helper functions etc. Maybe this should be private and these helper functions friend. Id expected is default constructible, why do we need it? What is the difference between
constexpr expected () and constexpr expected (value_t _) ?
The latter is there for emplacement construction for those willing to pay the compile time cost.
* default constructor
constexpr expected () noexcept(is_nothrow_default_constructible) Default constructor.
This doesn't say what the default constructor does. As per LEWG Expected, T is default constructed. The reference docs will be fixed with issue #26. I insists #26 has nothing to do with constructors and I'm (we're) interested in knowing how this will be fixed.
* Shouldn't
constexpr expected (in_place_t, std::initializer_list< U > l) noexcept(std::is_nothrow_constructible< value_type, std::initializer_list< U >>::value) Implicit constructor from an initializer list.
be
constexpr explicit expected(in_place_t, initializer_list<U> il, Args&&... args); Ah yes I had forgotten about that.
You are correct, but I left it unfixed to remind me to ask here what would be a suitable unit test for testing an "initializer_list<U> il, Args&&... args" constructor where the Args&&... gets used correctly. I was unsure of the semantics. Take a look at std::optional test if you are curious.
Also, it's logged to https://github.com/ned14/boost.outcome/issues/27.
* What is void_rebound and where it is defined It was accidentally omitted from the reference docs. It is "using void_rebound = rebind<void>". Logged to https://github.com/ned14/boost.outcome/issues/28.
* construction from error type
Why do you prefer an implicit conversion. This deviation must be in the rationale of the differences. The deviations list already explains that types T and E cannot be convertible into one another because we use simple overloading to avoid using SFINAE to keep compile time load low.
Code written for LEWG Expected will, unless types T and E are convertible into one another, work as expected. You don't answer to my question. Why implicit? What is wrong with the constructor
expected(unexpect_t, error) ?
* raw types
It is not clear what is the difference between xxx and raw_xxx Logged to https://github.com/ned14/boost.outcome/issues/29
Thanks for creating the issue, but I'm interested in knowing it now during the review. Otherwise I couldn't accept even conditionally the library.
* what is the sizeof expected <T,E>? It says already at the top of the page. It's max(24, sizeof(R)+8) on 64 bit CPUs.
An now that you will ensure the never empty warranties?
* What is expected<Policy> in Looks like a misparse by doxygen. Such a thing doesn't exist. Logged to https://github.com/ned14/boost.outcome/issues/30
* bool conversion should be explicit Another doxygen bug. Logged to https://github.com/ned14/boost.outcome/issues/31.
* is_ready has nothing to be with expected. Already removed from develop branch.
* Shouldn't value_or return by value? otherwise what would be the difference between get_or and value_or? The get_*() family of functions is scheduled to be removed as per peer review feedback by https://github.com/ned14/boost.outcome/issues/24
Okay.
* emplace. Missing overload
template <class U, class... Args> void emplace(initializer_list<U>, Args&&...); Great catch! Logged to https://github.com/ned14/boost.outcome/issues/32
* Missing preconditions on the observers, which make them wide operations. Saying that they return an address makes them wide operations however accessing to the contents of this address will be undefined behavior.
I prefer the standard way, defining a narrow function and stating clearly the pre-conditions. Issue #26 will fix these.
* what error_or <https://ned14.github.io/boost.outcome/structboost_1_1outcome_1_1v1__xxx_1_1policy_1_1expected__policy__base.html#ae0882c7d380b88b9011bcc60c59903bd> returns if it contains an exception? The or parameter. Errored does not include excepted, but excepted includes errored because an error code is emitted as an exception_ptr to a system_error containing that error code.
Hugh! Too complex.
* Missing overload for get_unexpected. In addition it doesn't return an unexpected_type.
constexpr const E & get_unexpected () const; I didn't realise there were rvalue, lvalue, const rvalue and const lvalue overloads. That must be a recent thing. Logged to https://github.com/ned14/boost.outcome/issues/33.
The return by E rather than expected<void, E> is by design. Remember we permit implicit construction from E, so this is safe and works well. Again, code written for LEWG Expected should never notice this implementation detail.
Why? if the signatures doesn't match I will have sure a problem somewhere.
(the real cause for this deviation is because the policy classes cannot return types still being fabricated, so they cannot return an expected<void, E>. If this deviation ever should become a problem, I can relocate get_unexpected to the main class and enable_if it, or else use a trampoline type. But I believe the existing form replicates the same semantics, it should be fine)
This is what I don't like of your library. In order to have a common implementation you sacrifice the concrete interfaces. I prefer to have concrete classes that provide the interface is the most adapted to these classes and then have an adaptation to some type of classes requirement when I need to do generic code. This something that I remarked since the beginning of your library. The basic_monad class is over-engineered IMHO.
Vicente thanks very much for such detailed feedback. It was very valuable.
Niall, please, don't wait until the review is finished to tell us how the issues will be fixed. You could do it on each issue and come back in this ML. Best, Vicente

Outcome doesn't do SFINAE on its main constructors. It relies on simple overloading which is much lower compile time cost. Maybe it has lower compile time, but it is not correct, isn't it? Would you suggest that std::expect shouldn't do SFINAE?
Outcome's Expected provides both a subset and a superset of your Expected proposal. I have promised to track closely your proposal paper, but I have no interest in providing a perfect match to your proposal. Outcome lets you seamlessly mix expected<T, E> with Outcomes and with *any arbitrary third party error handling system* thanks to the policy based core implementation. It therefore cannot exactly implement your proposal, it needs to differ because it *is* different. My claim is that any code written to use LEWG Expected will work exactly the same with Outcome's Expected. If it does not, I will repair Outcome's Expected until code using it works identically. I think this a very reasonable position to take, especially as you are still changing the proposed Expected. At the risk of slightly losing my temper, I need to say something I'll probably regret later when people throw axes at me. But it's been building for a few days now, and I need to vent. Both you and Vinnie have called Outcome "over engineered". I respectfully suggest neither of you understands the purpose of Outcome the **framework** which is to provide a very low overhead universal error handling framework. That's why there is the exact same CRTP policy based basic_monad class being typedefed into the aliases expected<T, E>, outcome<T>, result<T>, option<T>. They are all the same class and object, just with policy-determined "personality" that lets them provide differing semantics to each user, yet they all still operate seamlessly together and can be fed into one another e.g. via the TRY operation, and with very low, usually minimum, runtime overhead and low compile time overhead. If you are only in the market for just an expected<T, E> implementation and nothing else, then yes Outcome looks over engineered. But my claim is that in any real world code base of any size, people end up having to add layers on top of expected<T, E> etc to aid interop between parts of a large code base. They will have varying degrees of success, as people on Reddit have told me regarding their local expected<T, E> implementations. A lot of people end up with macros containing switch or try catch statements to map between differing error handling systems. And that is bad design. I will claim that if you *are* building such an interop framework, you will find that Outcome is the bare minimum implementation possible. It is, if anything, *under* engineered compared to the many other "universal error handling frameworks for C++" out there which tend to throw memory allocation and smart pointers and type erasure at the problem. Outcome doesn't do any of that, it never allocates memory, has highly predictable latency, runs perfectly with C++ exceptions disabled, is an excellent neighbour to all other C++ libraries AND build systems, and avoids where possible imposing any constraints on the user supplied types fed to it. It also lets you use as much or as little of itself as you choose. So okay, it's over engineered if you think you want just an expected<T, E>. But as soon as you roll expected<T, E> out into your code, you are going to find it won't be enough. Thus the rest of Outcome comes into play, and even then there are small gaps in what Outcome provides which any real world application will still need to fill. That's deliberate *under* engineering, I couldn't decide on what was best for everyone, so I give the end user the choice by providing many customisation points and macro hooks. I'll respond to your other comments in a separate reply. I just needed to unload the above, and I appreciate that the docs do not sufficiently get into the universal error handling framework part of Outcome. That is due to repeated Reddit feedback telling me that earlier editions of the docs didn't make sense, and I needed to go much slower and hold the hand, so you got the current tutorial which as you've already observed, is too long as it is. It would become even longer if you started dissecting universal error handling strategies. (And my thanks to Andrzej for the motivating example on the landing page of the docs, he did a great job capturing the universal error handling framework aspect of Outcome. I would love to know if reviewers can make sense of it, or did it just confuse the hell out of everybody) Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

On Sun, May 21, 2017 at 3:12 PM, Niall Douglas via Boost <boost@lists.boost.org> wrote:
Both you and Vinnie have called Outcome "over engineered". I respectfully suggest neither of you understands the purpose of Outcome the **framework**
When I raised the issue of requiring C++14 you made a point about handling obscure corner cases of exception safety and what not. Those are not necessarily the choices I would have made but your rationale sounded reasonable. What I consider "over-engineering" is monad_policy.ipp having macros that are set before being including three times to generate the source. It complicates the documentation toolchain as you well know, it confuses Visual Studio's intellisense, discourages casual reading of the source code, and presents an interesting challenge if the debugger brings you to a source code line in that file ("which class am I in again?). I would have just duplicated the source code with whatever differences the macros produce. The other thing I is the versioning namespace. It seems to me that a library only needs to introduce a version namespace upon publishing a second, incompatible interface. The initial version of a library can omit it. The consequences of this versioning namespace can be seen in Outcome's documentation. "v1xxx::" everywhere which is quite a blemish and also not something a user should ever have to type or see. Full disclosure: I have not delved deeply into the interface of Outcome, and I have not used it yet. But I don't anticipate that Outcome's interface will undergo change so significant that we need to lay out mentally cumbersome infrastructure in advance of those changes. Do you anticipate a need to support multiple incompatible API versions of Outcome? If so, how soon? And why do we need this infrastructure in the first version of Outcome? With respect to the error handling, memory allocation, policy concept in general I have no comment yet. Thanks

it confuses Visual Studio's intellisense,
Doesn't just confuse it, it murders it. I had to exit Visual Studio and delete my .vs directory for Intellisense to come back to life even after I removed any and all mentions of Outcome from the source.
This is very surprising and worrying. I genuinely have had zero issue with Intellisense and Outcome, and I use Visual Studio for everything including my Linux programming. I've been using Outcome for two years in Visual Studio, and seen no problems at all apart from the usual Intellisense ones. Just to test it there, I opened up AFIO v2 in VS2017, chose a random source file and on a line did: result<void>. ... and Intellisense correctly popped up all the possible member functions as it usually does. Each item is missing its documentation bubble, but I always found those virtually useless anyway, the doxygen comment doesn't fit. Are you guys doing something unusual? Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Niall Douglas wrote:
it confuses Visual Studio's intellisense, ...
Are you guys doing something unusual?
This is what I just did: in VS2017, I pasted this code: #include <boost/outcome/outcome.hpp> #include <system_error> #include <iostream> int main() { // boost::outcome::result<int> x( std::errc::operation_not_permitted ); boost::outcome::outcome<int> x = boost::outcome::make_errored_outcome( std::errc::operation_not_permitted ); if( x.has_value() ) { std::cout << "Value: " << x.value() << std::endl; } else { std::cout << "Error: " << x.error() << std::endl; } #if defined(_WINDOWS_) std::cout << "<windows.h> has been included\n"; #endif } and when I type x., Intellisense doesn't want to display anything.

Are you guys doing something unusual?
This is what I just did: in VS2017, I pasted this code: ...
Ah, mystery solved, I was still on the Clang toolset. That's when it dies. With the normal CL toolset, it works. And the problem does not seem caused by Outcome at all, so sorry about that.

On 21/05/2017 23:23, Vinnie Falco via Boost wrote:
What I consider "over-engineering" is monad_policy.ipp having macros that are set before being including three times to generate the source.
For those unclear what he means, Outcome stamps out variations of its policy classes using the preprocessor. We configure macros to set up the include, and include a template of the policy class, and do this many times throughout the codebase. More permutations are coming in order to address an issue raised during this review. Currently I think about seven, it may go to nine. Before anyone raises worries about effects on compile times, we partially preprocess Outcome's headers using my partial preprocessor pcpp. It expands out all this repeated reinclusion of files and any preprocessor logic it can safely execute immediately, passing through only the preprocessor logic determined by the compiler and runtime. A single header file is thus generated, and that's what is actually included when you include Outcome unless you're using C++ Modules. There is a macro to disable the single file header, and have the compiler do all of preprocessor magic per compiland.
It complicates the documentation toolchain as you well know,
The doxygen docs are generated by another partial preprocess of the Outcome headers to generate a simplified rendition suitable for doxygen's not great parser. It works well enough. doxygen ain't great at the best of times. You say it complicates the toolchain, but every C++ library I've ever written which uses metaprogramming needed to be preprocessed by a script into a format doxygen could grok. Historically it was Python. But this is no more complex than usual, it's working around doxygen. We've all had to do it many times in our careers.
it confuses Visual Studio's intellisense, discourages casual reading of the source code, and presents an interesting challenge if the debugger brings you to a source code line in that file ("which class am I in again?). I would have just duplicated the source code with whatever differences the macros produce.
I haven't seen the problems with Intellisense at all. You might be thinking of it drawing red lines under stuff. That's just because the IDE parser is not MSVC itself. The casual reading of the source code I personally find much easier than reading copy and pasted code with minor changes. Copy and pasting code with minor changes is a serious problem for later maintenance. Historically I would have used Python to stamp out the source code variants from a template, but because the preprocessor is good enough on all major C++ 14 compilers, I no longer have to.
The other thing I is the versioning namespace. It seems to me that a library only needs to introduce a version namespace upon publishing a second, incompatible interface. The initial version of a library can omit it.
ABI versioning, or rather the lack of it, has been a historical very sore spot for all users of Boost. It has caused countless thousand man hours to have been lost, generated unstable products in the hands of millions of consumers, and is a royal PITA. However under C++ 03 there was no easy alternative not involving external tooling or lots of macros, so it was understandable. But on C++ 11 we can do much better via inline namespaces to avoid ABI collisions, and Outcome implements that in full. I had thought Boost.Hana had done the same, but Louis appears to have adopted the same macroed namespace solution as my libraries, but not done the inline namespace with version.
The consequences of this versioning namespace can be seen in Outcome's documentation. "v1xxx::" everywhere which is quite a blemish and also not something a user should ever have to type or see.
One is trapped. It's an inline namespace, so it can be omitted in code. But it still exists, so it needs to be mentioned. The tutorial emphasises that you are advised to use the BOOST_OUTCOME_V1_NAMESPACE macro. That pins your code to the v1 API instead of whatever is "latest" in the current compiland.
Full disclosure: I have not delved deeply into the interface of Outcome, and I have not used it yet. But I don't anticipate that Outcome's interface will undergo change so significant that we need to lay out mentally cumbersome infrastructure in advance of those changes. Do you anticipate a need to support multiple incompatible API versions of Outcome? If so, how soon? And why do we need this infrastructure in the first version of Outcome?
As I discussed in an earlier thread with Peter, it is possible I got the default actions showstopper wrong. I don't think I have, but if I did, then the ABI versioning means breaking changes to fix a showstopper in the library will not break code. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Niall Douglas wrote:
... I just needed to unload the above, and I appreciate that the docs do not sufficiently get into the universal error handling framework part of Outcome. That is due to repeated Reddit feedback telling me that earlier editions of the docs didn't make sense, and I needed to go much slower and hold the hand, so you got the current tutorial which as you've already observed, is too long as it is.
For what it's worth, I found the docs understandable and useful. (Although the reference is a bit unwieldy, for a number of reasons.) If you could drop the remaining mentions of the word "monad" and any of its derivatives, it'd be even better. :-) Your first iteration was full of monad this and monad that, and embedded programmers who just want to return an error from a function are not very interested in monads. Taking a step back to the part of the message I snipped, the problem with expected<T, E> that makes it not suitable as a universal error handling strategy is the single E. In the real world, obviously lib1 will use E1 and lib2 will use E2, and the class needs to acknowledge this eventuality. That is why in my opinion the correct expected<T, E...> takes a variable number of possible Es, such that you could have result<T> == expected<T, error_code>, outcome<T> == expected<T, error_code, exception_ptr>, and make_expected(T) can return expected<T> (without any Es, because there aren't any.) I really need to write this thing down at some point before Work(tm) takes over and blows it away.

On Sun, May 21, 2017 at 3:12 PM, Niall Douglas via Boost <boost@lists.boost.org> wrote:
At the risk of slightly losing my temper, I need to say something I'll probably regret later when people throw axes at me. But it's been building for a few days now, and I need to vent.
Usually one hears the complaints much sooner than they hear the praise. On a positive note, there is considerable effort evident in Boost.Outcome. It solves a problem worth solving and reflects attention to details. Thank you for investing the time to develop this library and put it through the formal review process.

At the risk of slightly losing my temper, I need to say something I'll probably regret later when people throw axes at me. But it's been building for a few days now, and I need to vent.
Usually one hears the complaints much sooner than they hear the praise.
I think the response to my complaint about complaints over over-engineering was very useful. Some on Reddit foresaw outpourings of nastiness, and I am pleased to have proven them wrong. I had actually thought that your opinion of over-engineering was derived from design choices in Outcome which had nothing to do - at all - with the stuff you raised which I had thought self evident. So that was great to learn. But I forget that ABI versioning is new to Boost. libstdc++ has been doing it for years, and indeed my implementation clones libstdc++'s C++ 11 technique very closely. One assumes such features to be completely uncontroversial, but one is wrong.
On a positive note, there is considerable effort evident in Boost.Outcome. It solves a problem worth solving and reflects attention to details. Thank you for investing the time to develop this library and put it through the formal review process.
What drove me to invest the last six months of effort to turn Outcome from an open source library into a Boost candidate library was particularly to seek feedback on what is a wide and general topic affecting the future of C++ in general, hence the vitality of this review compared to most recent Boost library reviews: - Has a gap opened up in C++ between C++ exceptions and return codes which ought to be filled by something standardardised? - How important is extensibility in such a solution, and what exact forms should that extensibility take? - Should a solution be layers of increasing sophistication (class hierarchies), connected islands of individual implementation (Vicente's plan), one framework to rule them all (most open source C++ monadic programming solutions), single implementation/multiple personality (my design choice), or something entirely different again? - What tradeoff is appropriate between extra typing of boilerplate so programmers must spell out what they mean versus imposing a steeper learning curve on programmers to understand the primitives they are using? - How will choices made here and now benefit or damage all future C++ code? Will we in ten years look back and really wish this library and the entire philosophy of monadic return types had been rejected? Vicente has been very honest in saying here he isn't sure on any of this, and I echo the same sentiment: I'm not sure either. Outcome's design choice, even after this much review so far, I still feel to be the least worst of those I am aware are available, but be clear that none of the design approaches is without problems. So I totally get that others will disagree with the design choice, they feel they'd do it different, and I would be fairly sure that their totally different designs would probably be just fine as mine as far as end users are concerned. After all, what is the end user experience with Outcome? 95% won't care how it's implemented. They care about the cost to try it out, whether locking into this dependency will later turn out to be costly, whether the documentation is useful, and probably in that order. How stuff works under the bonnet doesn't matter so long as it doesn't bite them in the ass down the line in some particularly costly and unpredictable fashion. It's why I was persuaded by Reddit to drop documenting how to extend Outcome with your own custom personalities. It's fairly self evident from the (well commented) source code anyway for the < 5% who would ever be interested. I didn't go mad and choose some weird approach, the policy based design is 100% standard, conventional, layered CRTP. Any Boost dev should find extending easy. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Le 23/05/2017 à 13:47, Niall Douglas via Boost a écrit :
On a positive note, there is considerable effort evident in Boost.Outcome. It solves a problem worth solving and reflects attention to details. Thank you for investing the time to develop this library and put it through the formal review process. What drove me to invest the last six months of effort to turn Outcome from an open source library into a Boost candidate library was particularly to seek feedback on what is a wide and general topic affecting the future of C++ in general, hence the vitality of this review compared to most recent Boost library reviews:
I profit to recall that TBoost.Expected started as a not-accepted GSOC Boost project long time ago (May 2013).
- Has a gap opened up in C++ between C++ exceptions and return codes which ought to be filled by something standardardised?
I believe it.
- How important is extensibility in such a solution, and what exact forms should that extensibility take?
We don't know never what the user will have as errors, so I believe the mechanism must be extensible.
- Should a solution be layers of increasing sophistication (class hierarchies), connected islands of individual implementation (Vicente's plan), one framework to rule them all (most open source C++ monadic programming solutions), single implementation/multiple personality (my design choice), or something entirely different again?
- What tradeoff is appropriate between extra typing of boilerplate so programmers must spell out what they mean versus imposing a steeper learning curve on programmers to understand the primitives they are using? I believe that a lot of programmers are already the monadic approach without knowing they are using it in languages as Python. They just use
I believe the monadic stuff found in other functional languages (math) is a good base for the design. the design they see in others examples. Once we will have such interface in C++ (on boost or on the standard) we will start to see people to use it in a natural way.
- How will choices made here and now benefit or damage all future C++ code? Will we in ten years look back and really wish this library and the entire philosophy of monadic return types had been rejected?
Just a remark. Even if std::ex...::expected,std::optional and boost::outcome can be monads, there is nothing monadic until we introduce the monad::bind/chain or monad::unwrap/flatten functions? Your TRY macro play the role of the do-notation, but it works only for PossiblyValued types which std::ex...::expected,std::optional and boost::outcome are. There are other Monads (e.g. list) that will not work with the TRY macro.
Vicente has been very honest in saying here he isn't sure on any of this, and I echo the same sentiment: I'm not sure either. Outcome's design choice, even after this much review so far, I still feel to be the least worst of those I am aware are available, but be clear that none of the design approaches is without problems.
I agree with this last sentiment.
So I totally get that others will disagree with the design choice, they feel they'd do it different, and I would be fairly sure that their totally different designs would probably be just fine as mine as far as end users are concerned. User interface is important as well as how it can be configured (if needed at all). Changing the interface changes the user appreciation. I believe that before the implementation we are discussing here mainly of the user interface.
After all, what is the end user experience with Outcome? 95% won't care how it's implemented. They care about the cost to try it out, whether locking into this dependency will later turn out to be costly, whether the documentation is useful, and probably in that order. How stuff works under the bonnet doesn't matter so long as it doesn't bite them in the ass down the line in some particularly costly and unpredictable fashion. I don't care too much of the implementation as soon as I don't pay for what I don't use, and the implementation is not guiding the interface. However in a Boost review the implementation is also important, even if I take it after the interface, the design and the documentation.
It's why I was persuaded by Reddit to drop documenting how to extend Outcome with your own custom personalities. It's fairly self evident from the (well commented) source code anyway for the < 5% who would ever be interested. I didn't go mad and choose some weird approach, the policy based design is 100% standard, conventional, layered CRTP. Any Boost dev should find extending easy.
We are reviewing the library. If one of the major interest is that the library is configurable, the extension mechanism should be documented in order to be reviewed. Best, Vicente

Le 22/05/2017 à 00:12, Niall Douglas via Boost a écrit :
Outcome doesn't do SFINAE on its main constructors. It relies on simple overloading which is much lower compile time cost. Maybe it has lower compile time, but it is not correct, isn't it? Would you suggest that std::expect shouldn't do SFINAE? Outcome's Expected provides both a subset and a superset of your Expected proposal.
I have promised to track closely your proposal paper, but I have no interest in providing a perfect match to your proposal. Outcome lets you seamlessly mix expected<T, E> with Outcomes and with *any arbitrary third party error handling system* thanks to the policy based core implementation. It therefore cannot exactly implement your proposal, it needs to differ because it *is* different. My claim is that any code written to use LEWG Expected will work exactly the same with Outcome's Expected. If it does not, I will repair Outcome's Expected until code using it works identically. I think this a very reasonable position to take, especially as you are still changing the proposed Expected. You are right it is moving and not yet accepted. One of the major interest I have in this review is to try to see what can be improved on the proposed std expected.
At the risk of slightly losing my temper, I need to say something I'll probably regret later when people throw axes at me. But it's been building for a few days now, and I need to vent. I understand you. Both you and Vinnie have called Outcome "over engineered". I respectfully suggest neither of you understands the purpose of Outcome the **framework** which is to provide a very low overhead universal error handling framework. That's why there is the exact same CRTP policy based basic_monad class being typedefed into the aliases expected<T, E>, outcome<T>, result<T>, option<T>. They are all the same class and object, just with policy-determined "personality" that lets them provide differing semantics to each user, yet they all still operate seamlessly together and can be fed into one another e.g. via the TRY operation, and with very low, usually minimum, runtime overhead and low compile time overhead. Okay. Has this clearly stated and showed throw examples in the documentation? Sorry, I have not read all the documentation yet. IIUC, what you want is to have outcomes that copy/move well each one on each other. Is this the major drawback you find to the use of std::expected and std::optional? If you are only in the market for just an expected<T, E> implementation and nothing else, then yes Outcome looks over engineered. But my claim is that in any real world code base of any size, people end up having to add layers on top of expected<T, E> etc to aid interop between parts of a large code base. I recognize there is a problem when we need to forwarding errors that are transformed. This is my TODO plan for the standard proposals. Maybe my approach would be what you consider is the way we shouldn't follow. We will see. They will have varying degrees of success, as people on Reddit have told me regarding their local expected<T, E> implementations. A lot of people end up with macros containing switch or try catch statements to map between differing error handling systems. And that is bad design. This transformation can be hidden by higher level abstractions, but IMO they should be invoked explicitly. I will claim that if you *are* building such an interop framework, you will find that Outcome is the bare minimum implementation possible. Maybe or maybe not. If outcome uses more storage than expected or optional it is not the base minimum. It is, if anything, *under* engineered compared to the many other "universal error handling frameworks for C++" out there which tend to throw memory allocation and smart pointers and type erasure at the problem. Niall sorry, I don't like the worlds "universal" and similar qualifications as "ultra-lightweight error handling" and "minimum overhead universal outcome transport mechanism for C++" To what other "universal error handling are you referring? Ho wis your design universal? Outcome doesn't do any of that, it never allocates memory, has highly predictable latency, I hope so :) runs perfectly with C++ exceptions disabled, This is an interesting point. I will add it to the goal of your library. What is the behavior on the absence of exception on the functions that throw exceptions? Are these functions disabled? Does the fucntion terminate? Calls a handler? is an excellent neighbour to all other C++ libraries as for example? AND build systems, I'm less concerned by the build systems, but could you elaborate? and avoids where possible imposing any constraints on the user supplied types fed to it. It also lets you use as much or as little of itself as you choose. It is not imposing the use of boost::outcome? Does it interact well with std::expected and std::optional and boost::optional? I see that you responded in another mail No. So okay, it's over engineered if you think you want just an expected<T, E>. But as soon as you roll expected<T, E> out into your code, you are going to find it won't be enough. Thus the rest of Outcome comes into play, and even then there are small gaps in what Outcome provides which any real world application will still need to fill. That's deliberate *under* engineering, I couldn't decide on what was best for everyone, so I give the end user the choice by providing many customisation points and macro hooks.
I must recognize I'm reluctant to the basic_monad abstraction, and of course if the library is accepted should change of name. I'll need to take a deeper look, but I don't know if we are reviewing basic_monad it or if it is there by accident?
I'll respond to your other comments in a separate reply. I just needed to unload the above,
I understand.
and I appreciate that the docs do not sufficiently get into the universal error handling framework part of Outcome. That is due to repeated Reddit feedback telling me that earlier editions of the docs didn't make sense, and I needed to go much slower and hold the hand, so you got the current tutorial which as you've already observed, is too long as it is. It would become even longer if you started dissecting universal error handling strategies. If the main goal is the universal error handling strategies, then the documentation must describe what this is? (And my thanks to Andrzej for the motivating example on the landing page of the docs, he did a great job capturing the universal error handling framework aspect of Outcome. I would love to know if reviewers can make sense of it, or did it just confuse the hell out of everybody)
Could you tell us which example? Best, Vicente

Outcome's Expected provides both a subset and a superset of your Expected proposal.
I have promised to track closely your proposal paper, but I have no interest in providing a perfect match to your proposal. Outcome lets you seamlessly mix expected<T, E> with Outcomes and with *any arbitrary third party error handling system* thanks to the policy based core implementation. It therefore cannot exactly implement your proposal, it needs to differ because it *is* different. My claim is that any code written to use LEWG Expected will work exactly the same with Outcome's Expected. If it does not, I will repair Outcome's Expected until code using it works identically. I think this a very reasonable position to take, especially as you are still changing the proposed Expected.
You are right it is moving and not yet accepted. One of the major interest I have in this review is to try to see what can be improved on the proposed std expected.
I concur. If someone comes along with an obviously superior design to both Expected and Outcome, that would be an enormous win. (and sorry Peter, your expected<T, E...> design I am not persuaded by, but perhaps I am overestimating the brittle coupling generated by allowing every possible domain specific error type to bubble up to high level code)
Both you and Vinnie have called Outcome "over engineered". I respectfully suggest neither of you understands the purpose of Outcome the **framework** which is to provide a very low overhead universal error handling framework. That's why there is the exact same CRTP policy based basic_monad class being typedefed into the aliases expected<T, E>, outcome<T>, result<T>, option<T>. They are all the same class and object, just with policy-determined "personality" that lets them provide differing semantics to each user, yet they all still operate seamlessly together and can be fed into one another e.g. via the TRY operation, and with very low, usually minimum, runtime overhead and low compile time overhead.
Okay. Has this clearly stated and showed throw examples in the documentation? Sorry, I have not read all the documentation yet.
The landing page for the docs states the above and gives a motivating sample of code to show it. I don't know what else I can say to communicate this.
IIUC, what you want is to have outcomes that copy/move well each one on each other.
That was exactly a motivating reason for choosing this design originally. Having the same base storage implementation should allow the compiler to completely eliminate memory copying to implement layout changes when a personality is changed.
Is this the major drawback you find to the use of std::expected and std::optional?
No, not at all. Very recent optimisers e.g. in better than clang 4.0 and in GCC 6.0 with -O3 optimisation turn out to have rendered the common storage design choice no longer appropriate. But that wasn't the case when I began Outcome two years ago.
If you are only in the market for just an expected<T, E> implementation and nothing else, then yes Outcome looks over engineered. But my claim is that in any real world code base of any size, people end up having to add layers on top of expected<T, E> etc to aid interop between parts of a large code base.
I recognize there is a problem when we need to forwarding errors that are transformed. This is my TODO plan for the standard proposals. Maybe my approach would be what you consider is the way we shouldn't follow. We will see.
Actually I think that your proposal for this is very interesting with a lot of potential. Yours is much more powerful than the very limited, unambitious, almost simple "intrusive" interop that I've chosen. But your proposal is some years away from being production ready I think. There are lots of corner cases and quandaries which need to be resolved before people should start using it in code intended for long term usage. Between now and then, here is Outcome. It is an impoverished experience compared to your proposal, no doubt. But it has the advantage of relative simplicity and it's ready for use now, not later.
They will have varying degrees of success, as people on Reddit have told me regarding their local expected<T, E> implementations. A lot of people end up with macros containing switch or try catch statements to map between differing error handling systems. And that is bad design.
This transformation can be hidden by higher level abstractions, but IMO they should be invoked explicitly.
I agree that a user explicitly chooses to use result<T> instead of an expected<T> to explicitly opt into default actions to save boilerplate. That's an explicit choice of a higher level abstraction.
I will claim that if you *are* building such an interop framework, you will find that Outcome is the bare minimum implementation possible.
Maybe or maybe not. If outcome uses more storage than expected or optional it is not the base minimum.
As already described, Outcome consumes minimum overhead. To prove this I wrote this small program on godbolt: printf("%d\n", sizeof(std::optional<char>)); printf("%d\n", sizeof(std::optional<int>)); printf("%d\n", sizeof(std::optional<size_t>)); The answers are 2, 8 and 16 bytes. I wrote this just there in Outcome: printf("%d\n", sizeof(option<char>)); printf("%d\n", sizeof(option<int>)); printf("%d\n", sizeof(option<size_t>)); The answers are 2, 8 and 16 bytes. Identical overhead.
It is, if anything, *under* engineered compared to the many other "universal error handling frameworks for C++" out there which tend to throw memory allocation and smart pointers and type erasure at the problem.
Niall sorry, I don't like the worlds "universal" and similar qualifications as "ultra-lightweight error handling" and "minimum overhead universal outcome transport mechanism for C++" To what other "universal error handling are you referring? Ho wis your design universal?
You could wholly replace all usage of C++ exceptions with Outcome without losing fidelity of error information. (I wouldn't advise that you should, but you can) You would thus exchange (slightly) worse performance of successful code in exchange for vastly better and predictable performance of unsuccessful code. Note that the cost of throwing and catching a C++ exception with a table based EH C++ compiler is highly unpredictable, and costs between 1500-3000 CPU cycles per stack frame unwound between the try and catch site. You can find the benchmark in the benchmark directory in Outcome's git repo.
What is the behavior on the absence of exception on the functions that throw exceptions? Are these functions disabled? Does the fucntion terminate? Calls a handler?
All exceptions Outcome ever throws are done via a user redefinable macro. Those macros are listed at https://ned14.github.io/boost.outcome/md_doc_md_04-tutorial_c.html. If C++ exceptions are enabled, the default macro definition throws the exception. If C++ exceptions are disabled, the default macro definition prints a descriptive message and a stacktrace to stderr and calls std::terminate(). The unit test suite is compiled with C++ exceptions disabled and executed per commit by both Travis and Appveyor to make sure all the conformance and behaviours still work correctly.
is an excellent neighbour to all other C++ libraries
as for example?
It pollutes no namespaces, interferes in no way with other C++ including other versions of itself. You can mix multiple versions of Outcome in the same binary safely.
AND build systems,
I'm less concerned by the build systems, but could you elaborate?
Outcome doesn't require any magic macros predefined and can be used by end users simply by dropping a tarball of the library into their source code and getting to work. Outcome is my first non-toy library to not require non-C++ tooling to be built. It uses the preprocessor instead. If you are cmake based, Outcome's cmake is modern cmake 3 throughout and ticks every box in how modern cmake should be designed and written and consumed by arbitrary third party cmake. It lacks some cmake support like for the cmake package registry and (still!) make install, but the reason I haven't fixed those yet is that cmake usage is the enormously easy: add_subdirectory( "${CMAKE_CURRENT_SOURCE_DIR}/boost.outcome" # path to outcome source "${CMAKE_CURRENT_BINARY_DIR}/boost.outcome" # your choice of where to put binaries EXCLUDE_FROM_ALL # please only lazy build outcome on demand ) target_link_libraries(myexe PRIVATE boost::outcome::hl) No messing about with include paths, compiler flags, macros, reading documentation or anything. The above also only lazy builds the parts of Outcome used by your cmake projects on demand, and doesn't clutter generated IDE project files with anything but the minimum. Modern cmake is so amazingly better than v2 cmake. Stephen Kelly was one of the main people responsible for these improvements, he did a great job there.
and avoids where possible imposing any constraints on the user supplied types fed to it. It also lets you use as much or as little of itself as you choose.
It is not imposing the use of boost::outcome?
Not what I meant. I meant Outcome is designed so you can use parts of it without being obliged to use all of it.
So okay, it's over engineered if you think you want just an expected<T, E>. But as soon as you roll expected<T, E> out into your code, you are going to find it won't be enough. Thus the rest of Outcome comes into play, and even then there are small gaps in what Outcome provides which any real world application will still need to fill. That's deliberate *under* engineering, I couldn't decide on what was best for everyone, so I give the end user the choice by providing many customisation points and macro hooks.
I must recognize I'm reluctant to the basic_monad abstraction, and of course if the library is accepted should change of name.
I've already removed "monad" from all the documented types making up the public API in develop branch. Changing basic_monad is a week or two of work because it must be done by hand, I cannot automate the name change. So I leave it until after the review. If Outcome is rejected I won't bother wasting so much time. After all, basic_monad is not a public facing type, it only appears in the debugger. And the name is just a bunch of ASCII characters.
and I appreciate that the docs do not sufficiently get into the universal error handling framework part of Outcome. That is due to repeated Reddit feedback telling me that earlier editions of the docs didn't make sense, and I needed to go much slower and hold the hand, so you got the current tutorial which as you've already observed, is too long as it is. It would become even longer if you started dissecting universal error handling strategies. If the main goal is the universal error handling strategies, then the documentation must describe what this is?
That's a very nebulous topic to discuss in documentation. Look at this discussion thread between me and you over this past week. Imagine summarising that into documentation that both you and I agree with, AND is intelligible to the average programmer, AND does not form a document sized like a small book. Very, very hard.
(And my thanks to Andrzej for the motivating example on the landing page of the docs, he did a great job capturing the universal error handling framework aspect of Outcome. I would love to know if reviewers can make sense of it, or did it just confuse the hell out of everybody)
Could you tell us which example?
It's the landing page motivating code example. Bottom of https://ned14.github.io/boost.outcome/index.html. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Niall Douglas wrote:
(and sorry Peter, your expected<T, E...> design I am not persuaded by, but perhaps I am overestimating the brittle coupling generated by allowing every possible domain specific error type to bubble up to high level code)
This is not a design I personally find appealing - it's the equivalent of checked exceptions - but the fact remains that different libraries use different Es. Not a problem for result/outcome because the E is fixed.
If someone comes along with an obviously superior design to both Expected and Outcome, that would be an enormous win.
There's nothing much to improve upon in result/outcome as long as 4/5 of the stuff is thrown out. template<class T> class result { public: result(); // T() or error, legitimate fork result( T const& ); result( T&& ); result( std::error_code const& ) noexcept; result( result const& ); result( result&& ); result( outcome const& ); //? result( outcome && ); //? bool has_value() const noexcept; bool has_error() const noexcept; T value() const; T value() &&; std::error_code error() const; explicit operator bool() const noexcept; void swap( result& ) noexcept; }; That's literally it. As an extension, let's add the ring buffer: result( std::error_code& error, char const* message, uint32_t code1, uint32_t code2... ); result( std::error_code& error, extended_error_info const& info ); extended_error_info error_info() const; There we go.

If someone comes along with an obviously superior design to both Expected and Outcome, that would be an enormous win.
There's nothing much to improve upon in result/outcome as long as 4/5 of the stuff is thrown out.
template<class T> class result { public:
result(); // T() or error, legitimate fork
result( T const& ); result( T&& );
result( std::error_code const& ) noexcept;
result( result const& ); result( result&& );
result( outcome const& ); //? result( outcome && ); //?
bool has_value() const noexcept; bool has_error() const noexcept;
T value() const; T value() &&;
std::error_code error() const;
explicit operator bool() const noexcept;
void swap( result& ) noexcept; };
That's literally it.
You're missing a few bits of necessary stuff, like equality operators, initialiser list construction and so on. But essentially you've just specified Vicente's Expected there. If you watched my ACCU talk video, when I explain Expected I show the additional member functions over Optional on a second slide and it's only four or five additions. Peter do you think you might be happy if I extended Outcome's Expected implementation to instead be: template<class T, class EC = std::error_code, class E = void> class expected; Now expected<T, EC, E> would exactly mirror outcome/result/option, except with no empty state and with narrow contracts on the observers. Then it would be a perfect alternative to outcome/result/option. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Niall Douglas wrote:
template<class T> class result { public:
result(); // T() or error, legitimate fork
result( T const& ); result( T&& );
result( std::error_code const& ) noexcept;
result( result const& ); result( result&& );
result( outcome const& ); //? result( outcome && ); //?
bool has_value() const noexcept; bool has_error() const noexcept;
T value() const; T value() &&;
std::error_code error() const;
explicit operator bool() const noexcept;
void swap( result& ) noexcept; };
That's literally it.
You're missing a few bits of necessary stuff, like equality operators, initialiser list construction and so on.
Equality yes, initializer list no. This works: #include <vector> #include <system_error> template<class T> class result { private: T t_; public: result( T const& t ): t_( t ) { } result( T&& t ): t_( std::move( t ) ) { } result( std::error_code const & ) { } }; int main() { result<std::vector<int>> r{ { 1, 2, 3 } }; }
But essentially you've just specified Vicente's Expected there.
I don't think I have. Expected has more members.

Le 24/05/2017 à 16:52, Niall Douglas via Boost a écrit :
If someone comes along with an obviously superior design to both Expected and Outcome, that would be an enormous win. There's nothing much to improve upon in result/outcome as long as 4/5 of the stuff is thrown out.
template<class T> class result { public:
result(); // T() or error, legitimate fork
result( T const& ); result( T&& );
result( std::error_code const& ) noexcept;
result( result const& ); result( result&& );
result( outcome const& ); //? result( outcome && ); //?
bool has_value() const noexcept; bool has_error() const noexcept;
T value() const; T value() &&;
std::error_code error() const;
explicit operator bool() const noexcept;
void swap( result& ) noexcept; };
That's literally it. You're missing a few bits of necessary stuff, like equality operators, initialiser list construction and so on. But essentially you've just specified Vicente's Expected there.
If you watched my ACCU talk video, when I explain Expected I show the additional member functions over Optional on a second slide and it's only four or five additions.
Peter do you think you might be happy if I extended Outcome's Expected implementation to instead be:
template<class T, class EC = std::error_code, class E = void> class expected;
No please. Vicente

Niall Douglas wrote:
Peter do you think you might be happy if I extended Outcome's Expected implementation to instead be:
template<class T, class EC = std::error_code, class E = void> class expected;
No, I'm not overly concerned about Outcome's expected<> interface or implementation. It's a useful demonstration that your lower layer is flexible enough to produce it, but the main focus of your library are the result<> and outcome<> classes (and the error handling framework and philosophy they represent) as far as I'm concerned. I don't consider option(al)<> or expected<> a necessary part of that framework.

On Tue, May 23, 2017 at 7:37 AM, Niall Douglas via Boost < boost@lists.boost.org> wrote:
You could wholly replace all usage of C++ exceptions with Outcome without losing fidelity of error information.
(I wouldn't advise that you should, but you can)
You would thus exchange (slightly) worse performance of successful code in exchange for vastly better and predictable performance of unsuccessful code.
I keep seeing the assertion that C++ exceptions create performance problems but I am yet to see actual data to support it. Not a simple toy case but an actual program that was shown to be slow when using exceptions, that issue couldn't be fixed otherwise and the solution was to not use exceptions. Practically speaking exception handling overhead occurs only at function entry and exit. It disappears in thin air (all of it) by simply inlining the function, which would be done in performance-critical parts of the code anyway (and MSVC, whose exception handling overhead is probably the worst, can inline functions even at link time.) So I must ask: what is the practical problem solved by not using exceptions in general and Outcome in particular? It seems to me this is driven by a design preference for one coding style over another, which is fine, but nobody seems to be having that discussion. The cost of not using exceptions is that we're throwing away the ability to enforce postconditions. What's the upside? Emil

Le 23/05/2017 à 18:52, Emil Dotchevski via Boost a écrit :
On Tue, May 23, 2017 at 7:37 AM, Niall Douglas via Boost < boost@lists.boost.org> wrote:
You could wholly replace all usage of C++ exceptions with Outcome without losing fidelity of error information.
(I wouldn't advise that you should, but you can)
You would thus exchange (slightly) worse performance of successful code in exchange for vastly better and predictable performance of unsuccessful code.
I keep seeing the assertion that C++ exceptions create performance problems but I am yet to see actual data to support it. Not a simple toy case but an actual program that was shown to be slow when using exceptions, that issue couldn't be fixed otherwise and the solution was to not use exceptions.
Practically speaking exception handling overhead occurs only at function entry and exit. It disappears in thin air (all of it) by simply inlining the function, which would be done in performance-critical parts of the code anyway (and MSVC, whose exception handling overhead is probably the worst, can inline functions even at link time.)
So I must ask: what is the practical problem solved by not using exceptions in general and Outcome in particular? It seems to me this is driven by a design preference for one coding style over another, which is fine, but nobody seems to be having that discussion. The cost of not using exceptions is that we're throwing away the ability to enforce postconditions. What's the upside?
Emil, I see expected as an alternative to output return codes, not to exceptions. We have output return codes in Filesystem. I adopted them in Boost.Chrono and Boost.Thread. I regret it. If I have had expected at the time, I would had used expected. We can restrict the expected interface to avoid exception in some cases. This introduce other issues, as constructors can not return anything they must be replaced by factories that return expected. This impose a big change on the way we program in C++, but people that don't use exception have already to do something like that. expected makes all this error handling more uniform. Again, expected in not incompatible with exceptions. Vicente

I keep seeing the assertion that C++ exceptions create performance problems but I am yet to see actual data to support it. Not a simple toy case but an actual program that was shown to be slow when using exceptions, that issue couldn't be fixed otherwise and the solution was to not use exceptions.
As I already said, throwing and catching C++ exceptions on a table based EH implementation is very slow, and moreover, unpredictably so. It costs approx 1500-3000 CPU cycles per stack frame unwound. In this situation, using integer error codes, or error_code, or Outcome instead delivers huge improvements.
Practically speaking exception handling overhead occurs only at function entry and exit.
Only the case on non-table based EH implementations such as MSVC x86. Anything newer has zero, absolutely *zero* runtime overhead except for code bloat effect on cache exhaustion.
It disappears in thin air (all of it) by simply inlining the function, which would be done in performance-critical parts of the code anyway (and MSVC, whose exception handling overhead is probably the worst, can inline functions even at link time.)
Actually MSVC's exception handling performance with table based EH is by far the best of any of the major compilers. It's twice as fast as GCC for example. The next best is Apple XCode, and far behind is POSIX clang and GCC.
So I must ask: what is the practical problem solved by not using exceptions in general and Outcome in particular? It seems to me this is driven by a design preference for one coding style over another, which is fine, but nobody seems to be having that discussion. The cost of not using exceptions is that we're throwing away the ability to enforce postconditions. What's the upside?
If you are using integer return codes, the claim is that you should be using Expected/Outcome instead for much improved almost everything. My ACCU talk video covers this in depth. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

I keep seeing the assertion that C++ exceptions create performance problems but I am yet to see actual data to support it. Not a simple toy case but an actual program that was shown to be slow when using exceptions, that issue couldn't be fixed otherwise and the solution was to not use exceptions.
Emil, thanks for asking this question.
As I already said, throwing and catching C++ exceptions on a table based EH implementation is very slow, and moreover, unpredictably so. It costs approx 1500-3000 CPU cycles per stack frame unwound. In this situation, using integer error codes, or error_code, or Outcome instead delivers huge improvements.
Ok, so let's assume you have on average 10 stack frames, that brings us to not more than a few microseconds for handling a thrown exception. Now, how often do you throw exceptions (if used properly - for exceptional cases - that is)? In real world applications I'd assume that you don't throw more than (at the very max) a handful every second (and I'm sure this is already stretching it) - but please correct me if I'm wrong (I often am). Even this extreme case would cause less than a 1/10th of a percent of 'overhead' caused by throwing exceptions. IOW, you will not be able to measure the overhead introduced by throwing exceptions in real world code.
Practically speaking exception handling overhead occurs only at function entry and exit.
Only the case on non-table based EH implementations such as MSVC x86. Anything newer has zero, absolutely *zero* runtime overhead except for code bloat effect on cache exhaustion.
Could you clarify this please? Are you saying that using exceptions without throwing them doesn't cause any overhead nowadays anymore?
It disappears in thin air (all of it) by simply inlining the function, which would be done in performance-critical parts of the code anyway (and MSVC, whose exception handling overhead is probably the worst, can inline functions even at link time.)
Actually MSVC's exception handling performance with table based EH is by far the best of any of the major compilers. It's twice as fast as GCC for example. The next best is Apple XCode, and far behind is POSIX clang and GCC.
Again, please clarify. Does this mean that even if you throw exceptions (using MSVC) the overhead isn't too bad in the first place?
So I must ask: what is the practical problem solved by not using exceptions in general and Outcome in particular? It seems to me this is driven by a design preference for one coding style over another, which is fine, but nobody seems to be having that discussion. The cost of not using exceptions is that we're throwing away the ability to enforce postconditions. What's the upside?
If you are using integer return codes, the claim is that you should be using Expected/Outcome instead for much improved almost everything. My ACCU talk video covers this in depth.
I'm not sure I understand what you mean by 'for much improved almost everything'. Could you summarize your ACCU talk in two sentences for me, please? So, Emil's initial question is still unanswered (at least for me, and please feel free to just point me to an email thread or similar which already succinctly answers this - I apologize if I missed that): what is the use case for the outcome library? Replacing exceptions for error handling? If yes, why? Replacing the use of error codes for error handling? If yes, why not using exceptions? I'd appreciate any possible clarification on this matter as I'm growing more and more confused by the ongoing discussions. Thanks! Regards Hartmut --------------- http://boost-spirit.com http://stellar.cct.lsu.edu

2017-05-24 15:01 GMT+02:00 Hartmut Kaiser via Boost <boost@lists.boost.org>:
I keep seeing the assertion that C++ exceptions create performance problems but I am yet to see actual data to support it. Not a simple toy case but an actual program that was shown to be slow when using exceptions, that issue couldn't be fixed otherwise and the solution was to not use exceptions.
Emil, thanks for asking this question.
As I already said, throwing and catching C++ exceptions on a table based EH implementation is very slow, and moreover, unpredictably so. It costs approx 1500-3000 CPU cycles per stack frame unwound. In this situation, using integer error codes, or error_code, or Outcome instead delivers huge improvements.
Ok, so let's assume you have on average 10 stack frames, that brings us to not more than a few microseconds for handling a thrown exception.
Now, how often do you throw exceptions (if used properly - for exceptional cases - that is)? In real world applications I'd assume that you don't throw more than (at the very max) a handful every second (and I'm sure this is already stretching it) - but please correct me if I'm wrong (I often am). Even this extreme case would cause less than a 1/10th of a percent of 'overhead' caused by throwing exceptions.
IOW, you will not be able to measure the overhead introduced by throwing exceptions in real world code.
Practically speaking exception handling overhead occurs only at function entry and exit.
Only the case on non-table based EH implementations such as MSVC x86. Anything newer has zero, absolutely *zero* runtime overhead except for code bloat effect on cache exhaustion.
Could you clarify this please? Are you saying that using exceptions without throwing them doesn't cause any overhead nowadays anymore?
It disappears in thin air (all of it) by simply inlining the function, which would be done in performance-critical parts of the code anyway (and MSVC, whose exception handling overhead is probably the worst, can inline functions even at link time.)
Actually MSVC's exception handling performance with table based EH is by far the best of any of the major compilers. It's twice as fast as GCC for example. The next best is Apple XCode, and far behind is POSIX clang and GCC.
Again, please clarify. Does this mean that even if you throw exceptions (using MSVC) the overhead isn't too bad in the first place?
So I must ask: what is the practical problem solved by not using exceptions in general and Outcome in particular? It seems to me this is driven by a design preference for one coding style over another, which is fine, but nobody seems to be having that discussion. The cost of not using exceptions is that we're throwing away the ability to enforce postconditions. What's the upside?
If you are using integer return codes, the claim is that you should be using Expected/Outcome instead for much improved almost everything. My ACCU talk video covers this in depth.
I'm not sure I understand what you mean by 'for much improved almost everything'. Could you summarize your ACCU talk in two sentences for me, please?
So, Emil's initial question is still unanswered (at least for me, and please feel free to just point me to an email thread or similar which already succinctly answers this - I apologize if I missed that): what is the use case for the outcome library?
Replacing exceptions for error handling? If yes, why? Replacing the use of error codes for error handling? If yes, why not using exceptions?
I'd appreciate any possible clarification on this matter as I'm growing more and more confused by the ongoing discussions.
Maybe I can answer this. The most general answer is that Boost.Outcome is for people who want to reasonably decently report and handle failures and at the same time choose, or are forced, not to use exceptions. The reasons for not using exceptions may be different: - Real or imaginary concerns about performance - A personal style of programming - Company's policy to compile with exceptions disabled - Maintaining a code base that was never designed with exception-safety in mind - Making all control paths explicit, as opposed to having hidden control paths caused exceptions potentially thrown from any place - Parts of the programs/frameworks that themselves implement exception handling and cannot afford to use exceptions, like propagating failure reports across threads, tasks, fibers... Once you have decided not to use exceptions in some part of the program, Boost.Outcome offers convenient abstractions for you, likely superior to error codes. It implements 95% the currently being standardized `std::expected` library, plus a number of other useful additions. Regards, &rzej;

So, Emil's initial question is still unanswered (at least for me, and please feel free to just point me to an email thread or similar which already succinctly answers this - I apologize if I missed that): what is the use case for the outcome library?
Replacing exceptions for error handling? If yes, why? Replacing the use of error codes for error handling? If yes, why not using exceptions?
I'd appreciate any possible clarification on this matter as I'm growing more and more confused by the ongoing discussions.
Maybe I can answer this. The most general answer is that Boost.Outcome is for people who want to reasonably decently report and handle failures and at the same time choose, or are forced, not to use exceptions.
The reasons for not using exceptions may be different:
- Real or imaginary concerns about performance - A personal style of programming - Company's policy to compile with exceptions disabled - Maintaining a code base that was never designed with exception-safety in mind - Making all control paths explicit, as opposed to having hidden control paths caused exceptions potentially thrown from any place - Parts of the programs/frameworks that themselves implement exception handling and cannot afford to use exceptions, like propagating failure reports across threads, tasks, fibers...
Once you have decided not to use exceptions in some part of the program, Boost.Outcome offers convenient abstractions for you, likely superior to error codes. It implements 95% the currently being standardized `std::expected` library, plus a number of other useful additions.
Thanks Andrzej, that's very insightful, exactly what I was looking for. May I suggest to use this paragraph as the motivating text for the library in its docs? Something like this would have saved a lot of head scratching (at least for me) to begin with. Also, if this is really what the library is about, may I suggest to remove all mentioning of exceptions from the library itself, its documentation, and the related examples? Regards Hartmut --------------- http://boost-spirit.com http://stellar.cct.lsu.edu

On Tue, May 23, 2017 at 6:38 PM, Niall Douglas via Boost < boost@lists.boost.org> wrote:
I keep seeing the assertion that C++ exceptions create performance problems but I am yet to see actual data to support it. Not a simple toy case but an actual program that was shown to be slow when using exceptions, that issue couldn't be fixed otherwise and the solution was to not use exceptions.
As I already said, throwing and catching C++ exceptions on a table based EH implementation is very slow, and moreover, unpredictably so. It costs approx 1500-3000 CPU cycles per stack frame unwound. In this situation, using integer error codes, or error_code, or Outcome instead delivers huge improvements.
I don't dispute that exception handling has overhead and that you can construct a toy example that shows that. However, people who choose to avoid exceptions make a bigger claim, something along the lines of "in my domain we can't afford exception handling overhead". I have _never_ seen hard data to support this. Moreover, in the above response you seem to be talking about the speed of the unwinding itself, not about the effect of exception handling on the general speed of the code (when exceptions are not thrown). This may be true but your reasoning is still rather abstract. Does anyone have a concrete program where reporting a failure by throwing would be too expensive? If not, what problem are we aiming to solve with Outcome? By the way, it would be very helpful if the examples in Outcome don't talk about failures to open files, especially if the objection to using exceptions is that their performance is "unpredictable".
Practically speaking exception handling overhead occurs only at function entry and exit.
Only the case on non-table based EH implementations such as MSVC x86. Anything newer has zero, absolutely *zero* runtime overhead except for code bloat effect on cache exhaustion.
Sure, I'm taking the worst case scenario. My claim is that 1) the speed overhead of exception handling is usually negligible even in that case and 2) when it is not it can be removed by inlining the function.
It disappears in thin air (all of it) by simply inlining the function, which would be done in performance-critical parts of the code anyway (and MSVC, whose exception handling overhead is probably the worst, can inline functions even at link time.)
Actually MSVC's exception handling performance with table based EH is by far the best of any of the major compilers. It's twice as fast as GCC for example. The next best is Apple XCode, and far behind is POSIX clang and GCC.
I agree that talking about actual compilers today is relevant and should be considered, so when I'm discussing exception handling overhead I do mean actual real world uses. That said, in the spirit of C++ we should think of exception handling abstractly, even not considering specific ABIs. After all, we don't talk about the overhead of using for loops vs. do-while, do we? It should be the same with exceptions. If I have: try { throw x(); } catch( x & ) { } there is no reason for any exception to be actually thrown. In principle, compilers should be able to see that the above is noop.
From this point of view, there should be no performance difference between returning an error code or throwing an exception, and if there is, that is a problem of the compiler or the ABI but not of the semantics of exception handling as defined by C++.
We shouldn't be inventing new styles of reporting failures to work around the shortcomings of C++ code generators, or at least we should openly state this as the main motivation.
So I must ask: what is the practical problem solved by not using exceptions in general and Outcome in particular? It seems to me this is driven by a design preference for one coding style over another, which is fine, but nobody seems to be having that discussion. The cost of not using exceptions is that we're throwing away the ability to enforce postconditions. What's the upside?
If you are using integer return codes, the claim is that you should be using Expected/Outcome instead for much improved almost everything. My ACCU talk video covers this in depth.
What I'm saying is that you shouldn't be using integer or any other error codes except to prevent C++ exceptions from crossing a language barrier. Emil

On 24/05/2017 20:54, Emil Dotchevski via Boost wrote:
I don't dispute that exception handling has overhead and that you can construct a toy example that shows that. However, people who choose to avoid exceptions make a bigger claim, something along the lines of "in my domain we can't afford exception handling overhead". I have _never_ seen hard data to support this.
I have seen two use cases to avoid exceptions: 1) Any hard error won't be handled, because it's highly unlikely, and we don't want to use more space or CPU (depending on the implementation) in something that is very unlikely. For hard errors std::terminate or a log-and-die solution is a perfectly valid option for an application (e.g. when memory is exhausted or a logic error is detected). This could be the case for some games. 2) Hidden control paths are unacceptable. Typically safety or security critical systems where every single path or branch must be tested. Explicitly error handling shows every path and code review and coverage tools are very helpful. Writing exception safe code is error-prone, specially when operations that can throw can change implicitly when inner operations of a statement start throwing new classes. The exception type thrown by is not enforced by the compiler whereas the return type is fixed at compile time. Sometimes this makes error handling much easier. Best, Ion

On Wed, May 24, 2017 at 12:16 PM, Ion Gaztañaga via Boost < boost@lists.boost.org> wrote:
On 24/05/2017 20:54, Emil Dotchevski via Boost wrote:
I don't dispute that exception handling has overhead and that you can construct a toy example that shows that. However, people who choose to avoid exceptions make a bigger claim, something along the lines of "in my domain we can't afford exception handling overhead". I have _never_ seen hard data to support this.
I have seen two use cases to avoid exceptions:
1) Any hard error won't be handled, because it's highly unlikely, and we don't want to use more space or CPU (depending on the implementation) in something that is very unlikely. For hard errors std::terminate or a log-and-die solution is a perfectly valid option for an application (e.g. when memory is exhausted or a logic error is detected). This could be the case for some games.
2) Hidden control paths are unacceptable. Typically safety or security critical systems where every single path or branch must be tested. Explicitly error handling shows every path and code review and coverage tools are very helpful. Writing exception safe code is error-prone, specially when operations that can throw can change implicitly when inner operations of a statement start throwing new classes. The exception type thrown by is not enforced by the compiler whereas the return type is fixed at compile time. Sometimes this makes error handling much easier.
Arguments similar to the ones you make in 2) can be made in support of exception handling as well. For example, returning error codes has many more points where bugs can occur, since failure-neutral contexts still must be concerned with failures. Under maintenance, unrelated seemingly innocent change in the control flow can easily introduce subtle bugs in the error-reporting branches. This problem is amplified by the fact that these branches are usually very difficult to test and analyze, therefore it is critical to adopt a programming style that avoids such bugs. I would conclude that writing exception-unsafe code is error-prone, both during the initial development and under maintenance. I would typically recommend writing exception-safe code even if you don't use exceptions. That said, you're making good points, both are arguments about the semantics of exception handling rather than the overhead of it. My initial objection if you want to call it that is that the general attitude of Outcome seems to be "exceptions are slow and so C++98, what are you, a neanderthal?" :) whereas I see Outcome as lowering the level of abstraction to where it is in other languages which force programmers to laboriously deal with failures explicitly (which is prone to errors) -- for no good reason at all.

construct a toy example that shows that. However, people who choose to avoid exceptions make a bigger claim, something along the lines of "in my domain we can't afford exception handling overhead". I have _never_ seen hard data to support this.
SG14 have tried to find where enabling or disabling C++ exceptions made any difference to performance so long as exceptions were not regularly thrown. They have found none. There are lots of other reasons to globally turn off C++ exceptions, but performance in the non-throwing case is not one.
Moreover, in the above response you seem to be talking about the speed of the unwinding itself, not about the effect of exception handling on the general speed of the code (when exceptions are not thrown).
No I am exclusively talking alone about the cost of throwing and catching exceptions. Non-throw induced unwinds I have found there to also be no difference in performance between C++ exceptions on versus off.
This may be true but your reasoning is still rather abstract. Does anyone have a concrete program where reporting a failure by throwing would be too expensive? If not, what problem are we aiming to solve with Outcome?
c.f. the AFIO v1 review some years ago where my view that the cost of throwing an exception was irrelevant compared to a disc i/o. I was still panned for that design choice. Because I believe in giving people what they want, we have arrived here with a generalised framework for avoiding exception throws entirely as was necessary for AFIO v2 given peer review feedback from here. So far, most people seem to feel that I have given them what they wanted. I am glad.
By the way, it would be very helpful if the examples in Outcome don't talk about failures to open files, especially if the objection to using exceptions is that their performance is "unpredictable".
File i/o failure can be both common and unpredictable. It's why AFIO v2 needed a very low overhead failure handling mechanism, and I use it with a vengeance in afio::algorithm::* the AFIO v2 filesystem algorithms library. Performance, especially on NVMe SSDs, is spectacular.
I agree that talking about actual compilers today is relevant and should be considered, so when I'm discussing exception handling overhead I do mean actual real world uses.
In real world code, the cost of the exception throw and catch mechanism will be dwarfed by work done during the unwind in most cases. So for the average case, just go with C++ exceptions and relax. But for some users, every possible execution path needs to have a hand allocated CPU cycle budget. For these users, Expected or Outcome is less error prone than integer error codes.
That said, in the spirit of C++ we should think of exception handling abstractly, even not considering specific ABIs. After all, we don't talk about the overhead of using for loops vs. do-while, do we? It should be the same with exceptions. If I have:
try { throw x(); } catch( x & ) { }
there is no reason for any exception to be actually thrown. In principle, compilers should be able to see that the above is noop.
Most of the time trivially obvious try...catch are turned into a branch to early exit by the compiler. But it's not consistent. I've seen very obvious trivial try...catch get turned into a full dive into the runtime for execution despite the obviously inlined code. I would assume an optimiser bug.
From this point of view, there should be no performance difference between returning an error code or throwing an exception, and if there is, that is a problem of the compiler or the ABI but not of the semantics of exception handling as defined by C++.
For some users, "should" isn't enough. They want *guaranteed*.
We shouldn't be inventing new styles of reporting failures to work around the shortcomings of C++ code generators, or at least we should openly state this as the main motivation.
Either monads are absolutely standard in Rust and Swift. They can be overused or misused just like anything else. But nobody is inventing anything here. We are cloning and putting another tool into developer's hands. Given the huge popularity of this review, I would say most agree. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

On Wed, May 24, 2017 at 2:30 PM, Niall Douglas via Boost < boost@lists.boost.org> wrote:
This may be true but your reasoning is still rather abstract. Does anyone have a concrete program where reporting a failure by throwing would be too expensive? If not, what problem are we aiming to solve with Outcome?
c.f. the AFIO v1 review some years ago where my view that the cost of throwing an exception was irrelevant compared to a disc i/o. I was still panned for that design choice.
This is presented as a matter of opinion. I was looking for something more objective, since I am familiar with the unsubstantiated "exceptions are slow!!!!" attitude (my background is in video game engines and no, there are no performance problems with throwing exceptions to report errors in games, despite what others claim without evidence.)
By the way, it would be very helpful if the examples in Outcome don't talk about failures to open files, especially if the objection to using exceptions is that their performance is "unpredictable".
File i/o failure can be both common and unpredictable. It's why AFIO v2 needed a very low overhead failure handling mechanism, and I use it with a vengeance in afio::algorithm::* the AFIO v2 filesystem algorithms library. Performance, especially on NVMe SSDs, is spectacular.
Spectacular, compared to what? Is it spectacular because it avoids throwing exceptions? When I mention the unpredictability of file operations, what I mean is that their performance is even _more_ unpredictable than the overhead of (the work done during) stack frame unwinding. It is wrong to be concerned with the unpredictability the performance of a throw in the case you're dealing with I/O.
I agree that talking about actual compilers today is relevant and should be considered, so when I'm discussing exception handling overhead I do mean actual real world uses.
In real world code, the cost of the exception throw and catch mechanism will be dwarfed by work done during the unwind in most cases. So for the average case, just go with C++ exceptions and relax.
But for some users, every possible execution path needs to have a hand allocated CPU cycle budget. For these users, Expected or Outcome is less error prone than integer error codes.
I'm still looking for these use cases. I keep hearing they exist, I am sure they exist, and it would be very helpful if Outcome lists a few of them and shows how throwing exceptions fails spectacularly in these cases, rather than talking about I/O, in which case you seem to agree it's better to throw exceptions to report failures.
abstractly, even not considering specific ABIs. After all, we don't talk about the overhead of using for loops vs. do-while, do we? It should be
That said, in the spirit of C++ we should think of exception handling the
same with exceptions. If I have:
try { throw x(); } catch( x & ) { }
there is no reason for any exception to be actually thrown. In principle, compilers should be able to see that the above is noop.
Most of the time trivially obvious try...catch are turned into a branch to early exit by the compiler. But it's not consistent. I've seen very obvious trivial try...catch get turned into a full dive into the runtime for execution despite the obviously inlined code. I would assume an optimiser bug.
The other possibility is ABI restriction. Either way, not a problem for the C++ definition itself.
From this point of view, there should be no performance difference between returning an error code or throwing an exception, and if there is, that is a problem of the compiler or the ABI but not of the semantics of exception handling as defined by C++.
For some users, "should" isn't enough. They want *guaranteed*.
It is not possible to guarantee that the optimizer will do a good job optimizing a for loop either, that's why I said "should". If your point is that it's more likely for a code generator to deal efficiently with a for loop than with throw..catch, that is probably true, but I didn't think that Outcome is designed as a workaround for the deficiencies of existing optimizers. Maybe all this means is that Outcome needs better examples, but I still don't understand its motivation. Or maybe it's as simple as "some people religiously avoid throwing exceptions and this is better than what they would do otherwise" Emil

Le 23/05/2017 à 16:37, Niall Douglas via Boost a écrit :
Outcome's Expected provides both a subset and a superset of your Expected proposal.
I have promised to track closely your proposal paper, but I have no interest in providing a perfect match to your proposal. Outcome lets you seamlessly mix expected<T, E> with Outcomes and with *any arbitrary third party error handling system* thanks to the policy based core implementation. It therefore cannot exactly implement your proposal, it needs to differ because it *is* different. My claim is that any code written to use LEWG Expected will work exactly the same with Outcome's Expected. If it does not, I will repair Outcome's Expected until code using it works identically. I think this a very reasonable position to take, especially as you are still changing the proposed Expected. You are right it is moving and not yet accepted. One of the major interest I have in this review is to try to see what can be improved on the proposed std expected. I concur. If someone comes along with an obviously superior design to both Expected and Outcome, that would be an enormous win.
(and sorry Peter, your expected<T, E...> design I am not persuaded by, but perhaps I am overestimating the brittle coupling generated by allowing every possible domain specific error type to bubble up to high level code)
Both you and Vinnie have called Outcome "over engineered". I respectfully suggest neither of you understands the purpose of Outcome the **framework** which is to provide a very low overhead universal error handling framework. That's why there is the exact same CRTP policy based basic_monad class being typedefed into the aliases expected<T, E>, outcome<T>, result<T>, option<T>. They are all the same class and object, just with policy-determined "personality" that lets them provide differing semantics to each user, yet they all still operate seamlessly together and can be fed into one another e.g. via the TRY operation, and with very low, usually minimum, runtime overhead and low compile time overhead. Okay. Has this clearly stated and showed throw examples in the documentation? Sorry, I have not read all the documentation yet. The landing page for the docs states the above and gives a motivating sample of code to show it. I don't know what else I can say to communicate this.
IIUC, what you want is to have outcomes that copy/move well each one on each other. That was exactly a motivating reason for choosing this design originally. Having the same base storage implementation should allow the compiler to completely eliminate memory copying to implement layout changes when a personality is changed. How outcome ovoids this copying?
Is this the major drawback you find to the use of std::expected and std::optional? No, not at all. Very recent optimisers e.g. in better than clang 4.0 and in GCC 6.0 with -O3 optimisation turn out to have rendered the common storage design choice no longer appropriate. But that wasn't the case when I began Outcome two years ago. And then?
If you are only in the market for just an expected<T, E> implementation and nothing else, then yes Outcome looks over engineered. But my claim is that in any real world code base of any size, people end up having to add layers on top of expected<T, E> etc to aid interop between parts of a large code base. I recognize there is a problem when we need to forwarding errors that are transformed. This is my TODO plan for the standard proposals. Maybe my approach would be what you consider is the way we shouldn't follow. We will see. Actually I think that your proposal for this is very interesting with a lot of potential. Yours is much more powerful than the very limited, unambitious, almost simple "intrusive" interop that I've chosen.
But your proposal is some years away from being production ready I think. There are lots of corner cases and quandaries which need to be resolved before people should start using it in code intended for long term usage. Well, all this depends on how many people is contributing to the
Glad to hear that :) proposal. If we are able to work all on the same direction we can add the efforts and arrive sonner. Anyway, you can start playing with it using my std_make repository. If you find that there is something missing or erroneous please create an issue.
Between now and then, here is Outcome. It is an impoverished experience compared to your proposal, no doubt. But it has the advantage of relative simplicity and it's ready for use now, not later.
I believe that my library is not ready to be used, not because it doesn't exists, but because I want to be free to change it when a better design is found.
They will have varying degrees of success, as people on Reddit have told me regarding their local expected<T, E> implementations. A lot of people end up with macros containing switch or try catch statements to map between differing error handling systems. And that is bad design. This transformation can be hidden by higher level abstractions, but IMO they should be invoked explicitly. I agree that a user explicitly chooses to use result<T> instead of an expected<T> to explicitly opt into default actions to save boilerplate. That's an explicit choice of a higher level abstraction.
I will claim that if you *are* building such an interop framework, you will find that Outcome is the bare minimum implementation possible. Maybe or maybe not. If outcome uses more storage than expected or optional it is not the base minimum. As already described, Outcome consumes minimum overhead. To prove this I wrote this small program on godbolt:
printf("%d\n", sizeof(std::optional<char>)); printf("%d\n", sizeof(std::optional<int>)); printf("%d\n", sizeof(std::optional<size_t>));
The answers are 2, 8 and 16 bytes.
I wrote this just there in Outcome:
printf("%d\n", sizeof(option<char>)); printf("%d\n", sizeof(option<int>)); printf("%d\n", sizeof(option<size_t>));
The answers are 2, 8 and 16 bytes. Identical overhead.
Glad to see that. What about expected<T,E>?
It is, if anything, *under* engineered compared to the many other "universal error handling frameworks for C++" out there which tend to throw memory allocation and smart pointers and type erasure at the problem. Niall sorry, I don't like the worlds "universal" and similar qualifications as "ultra-lightweight error handling" and "minimum overhead universal outcome transport mechanism for C++" To what other "universal error handling are you referring? Ho wis your design universal? You could wholly replace all usage of C++ exceptions with Outcome without losing fidelity of error information.
(I wouldn't advise that you should, but you can)
You would thus exchange (slightly) worse performance of successful code in exchange for vastly better and predictable performance of unsuccessful code.
Note that the cost of throwing and catching a C++ exception with a table based EH C++ compiler is highly unpredictable, and costs between 1500-3000 CPU cycles per stack frame unwound between the try and catch site. You can find the benchmark in the benchmark directory in Outcome's git repo.
So you are comparing to the use of exceptions here and not to the use of output error codes?
What is the behavior on the absence of exception on the functions that throw exceptions? Are these functions disabled? Does the fucntion terminate? Calls a handler? All exceptions Outcome ever throws are done via a user redefinable macro. Those macros are listed at https://ned14.github.io/boost.outcome/md_doc_md_04-tutorial_c.html.
If C++ exceptions are enabled, the default macro definition throws the exception.
If C++ exceptions are disabled, the default macro definition prints a descriptive message and a stacktrace to stderr and calls std::terminate().
This is similar to what BOOST_THROW_EXCEPTION does, isn't it? Why re-invent the wheel?
The unit test suite is compiled with C++ exceptions disabled and executed per commit by both Travis and Appveyor to make sure all the conformance and behaviours still work correctly.
is an excellent neighbour to all other C++ libraries as for example? It pollutes no namespaces, interferes in no way with other C++ including other versions of itself. You can mix multiple versions of Outcome in the same binary safely. There are several version of Outcome?
AND build systems, I'm less concerned by the build systems, but could you elaborate? Outcome doesn't require any magic macros predefined and can be used by end users simply by dropping a tarball of the library into their source code and getting to work. While this could be a quality for a standalone library, this is not a quality for a Boost library.
Outcome is my first non-toy library to not require non-C++ tooling to be built. It uses the preprocessor instead. :(
If you are cmake based, Outcome's cmake is modern cmake 3 throughout and ticks every box in how modern cmake should be designed and written and consumed by arbitrary third party cmake. It lacks some cmake support like for the cmake package registry and (still!) make install, but the reason I haven't fixed those yet is that cmake usage is the enormously easy:
add_subdirectory( "${CMAKE_CURRENT_SOURCE_DIR}/boost.outcome" # path to outcome source "${CMAKE_CURRENT_BINARY_DIR}/boost.outcome" # your choice of where to put binaries EXCLUDE_FROM_ALL # please only lazy build outcome on demand ) target_link_libraries(myexe PRIVATE boost::outcome::hl)
No messing about with include paths, compiler flags, macros, reading documentation or anything. The above also only lazy builds the parts of Outcome used by your cmake projects on demand, and doesn't clutter generated IDE project files with anything but the minimum.
Modern cmake is so amazingly better than v2 cmake. Stephen Kelly was one of the main people responsible for these improvements, he did a great job there.
and avoids where possible imposing any constraints on the user supplied types fed to it. It also lets you use as much or as little of itself as you choose. It is not imposing the use of boost::outcome? Not what I meant. I meant Outcome is designed so you can use parts of it without being obliged to use all of it.
So okay, it's over engineered if you think you want just an expected<T, E>. But as soon as you roll expected<T, E> out into your code, you are going to find it won't be enough. Thus the rest of Outcome comes into play, and even then there are small gaps in what Outcome provides which any real world application will still need to fill. That's deliberate *under* engineering, I couldn't decide on what was best for everyone, so I give the end user the choice by providing many customisation points and macro hooks. I must recognize I'm reluctant to the basic_monad abstraction, and of course if the library is accepted should change of name. I've already removed "monad" from all the documented types making up the public API in develop branch.
Changing basic_monad is a week or two of work because it must be done by hand, I cannot automate the name change. So I leave it until after the review. If Outcome is rejected I won't bother wasting so much time. After all, basic_monad is not a public facing type, it only appears in the debugger. And the name is just a bunch of ASCII characters. It appears on the documentation :(
and I appreciate that the docs do not sufficiently get into the universal error handling framework part of Outcome. That is due to repeated Reddit feedback telling me that earlier editions of the docs didn't make sense, and I needed to go much slower and hold the hand, so you got the current tutorial which as you've already observed, is too long as it is. It would become even longer if you started dissecting universal error handling strategies. If the main goal is the universal error handling strategies, then the documentation must describe what this is? That's a very nebulous topic to discuss in documentation. Look at this discussion thread between me and you over this past week. Imagine summarising that into documentation that both you and I agree with, AND is intelligible to the average programmer, AND does not form a document sized like a small book.
Very, very hard. Add it on an Extension section.
(And my thanks to Andrzej for the motivating example on the landing page of the docs, he did a great job capturing the universal error handling framework aspect of Outcome. I would love to know if reviewers can make sense of it, or did it just confuse the hell out of everybody)
Could you tell us which example? It's the landing page motivating code example. Bottom of https://ned14.github.io/boost.outcome/index.html.
No this example doesn't show any advantage of Boost.Outcome, quite the opposite. However the other example that Andrzej has posted recently is a better example as I have already said as a response. Best, Vicente

IIUC, what you want is to have outcomes that copy/move well each one on each other. That was exactly a motivating reason for choosing this design originally. Having the same base storage implementation should allow the compiler to completely eliminate memory copying to implement layout changes when a personality is changed.
How outcome ovoids this copying?
Compilers spotted the common outcome::value_storage during moves when it was identical, and totally eliminated any memory copies.
Is this the major drawback you find to the use of std::expected and std::optional? No, not at all. Very recent optimisers e.g. in better than clang 4.0 and in GCC 6.0 with -O3 optimisation turn out to have rendered the common storage design choice no longer appropriate. But that wasn't the case when I began Outcome two years ago.
And then?
clang 4.0+ and GCC 6.0+ with -O3 optimisation appear to now be clever enough to spot trivial move constructors and trivial types, and not copy memory when the bits in memory would not change. I would therefore expect that your reference Expected implementation would now be identical or very similar in runtime performance to Outcome on clang 4.0+ and GCC 6.0+. MSVC does, of course, still have some way to go yet, but VS2017 is a world of improvement over VS2015.
You would thus exchange (slightly) worse performance of successful code in exchange for vastly better and predictable performance of unsuccessful code.
Note that the cost of throwing and catching a C++ exception with a table based EH C++ compiler is highly unpredictable, and costs between 1500-3000 CPU cycles per stack frame unwound between the try and catch site. You can find the benchmark in the benchmark directory in Outcome's git repo.
So you are comparing to the use of exceptions here and not to the use of output error codes?
I am comparing the cost of throwing and catching exceptions to returning an expected with unexpected state.
What is the behavior on the absence of exception on the functions that throw exceptions? Are these functions disabled? Does the fucntion terminate? Calls a handler? All exceptions Outcome ever throws are done via a user redefinable macro. Those macros are listed at https://ned14.github.io/boost.outcome/md_doc_md_04-tutorial_c.html.
If C++ exceptions are enabled, the default macro definition throws the exception.
If C++ exceptions are disabled, the default macro definition prints a descriptive message and a stacktrace to stderr and calls std::terminate().
This is similar to what BOOST_THROW_EXCEPTION does, isn't it? Why re-invent the wheel?
End users can define the Outcome exception throwing macros to call BOOST_THROW_EXCEPTION if they wish. For standalone Outcome users, the default behaviour is minimal, but sufficient.
is an excellent neighbour to all other C++ libraries as for example? It pollutes no namespaces, interferes in no way with other C++ including other versions of itself. You can mix multiple versions of Outcome in the same binary safely.
There are several version of Outcome?
Currently every time I commit to the git repo there is a new version of Outcome. The ABI is permuted with the SHA of the git. This prevents unstable versions of Outcome conflicting with different unstable versions of Outcome in the same process. I will, at some future point, declare a stable ABI and pin it forever to v1 of the ABI and have that checked per commit by Travis.
AND build systems, I'm less concerned by the build systems, but could you elaborate? Outcome doesn't require any magic macros predefined and can be used by end users simply by dropping a tarball of the library into their source code and getting to work.
While this could be a quality for a standalone library, this is not a quality for a Boost library.
The other C++ 14 library in Boost does not require any of Boost either.
(And my thanks to Andrzej for the motivating example on the landing page of the docs, he did a great job capturing the universal error handling framework aspect of Outcome. I would love to know if reviewers can make sense of it, or did it just confuse the hell out of everybody)
Could you tell us which example? It's the landing page motivating code example. Bottom of https://ned14.github.io/boost.outcome/index.html.
No this example doesn't show any advantage of Boost.Outcome, quite the opposite. However the other example that Andrzej has posted recently is a better example as I have already said as a response.
The only difference was returning result instead of outcome, and removing the exception throwing function call. Otherwise the examples are identical. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

IIUC, what you want is to have outcomes that copy/move well each one on each other. That was exactly a motivating reason for choosing this design originally. Having the same base storage implementation should allow the compiler to completely eliminate memory copying to implement layout changes when a personality is changed. How outcome ovoids this copying? Compilers spotted the common outcome::value_storage during moves when it was identical, and totally eliminated any memory copies. Okay, so the single thing outcome::outcome provides is the conversion from the other Outcomes. I believe this is great and I will add it to the open question for std::expected as I've already notified privately to you (waiting a response :) ) Is this the major drawback you find to the use of std::expected and std::optional? No, not at all. Very recent optimisers e.g. in better than clang 4.0 and in GCC 6.0 with -O3 optimisation turn out to have rendered the common storage design choice no longer appropriate. But that wasn't the case when I began Outcome two years ago. And then? clang 4.0+ and GCC 6.0+ with -O3 optimisation appear to now be clever enough to spot trivial move constructors and trivial types, and not copy memory when the bits in memory would not change. Do you mean that now we can use optional and expected without any
Le 24/05/2017 à 17:20, Niall Douglas via Boost a écrit : performance penalty and that the design of Outcome is not needed any more?
I would therefore expect that your reference Expected implementation would now be identical or very similar in runtime performance to Outcome on clang 4.0+ and GCC 6.0+. MSVC does, of course, still have some way to go yet, but VS2017 is a world of improvement over VS2015.
I was looking for any advantage of the design of Outcome ;-)
You would thus exchange (slightly) worse performance of successful code in exchange for vastly better and predictable performance of unsuccessful code.
Note that the cost of throwing and catching a C++ exception with a table based EH C++ compiler is highly unpredictable, and costs between 1500-3000 CPU cycles per stack frame unwound between the try and catch site. You can find the benchmark in the benchmark directory in Outcome's git repo. So you are comparing to the use of exceptions here and not to the use of output error codes? I am comparing the cost of throwing and catching exceptions to returning an expected with unexpected state.
Throwing an exception should be rare, exceptional, and most of the cases we could ignore them. expected is there to return expected values and possible errors, but these error are not necessarily exceptional and that could occur more often. I will never change a function that return TR and that can throw bad_alloc for a function that return expected<T, bad_alloc>. This is exceptional. I will not return expected for a function that could exceptionally detect that there is a hard disk error. This is exceptional. expected should cover the expected errors not the unexpected ones. Exceptional case should continue using exceptions. I don't want to transport this kind of exceptional situations in my interface. If the user cannot use exceptions for whatever reason, it could use expected to transport this exceptional situations as well, but IMO the exceptional situation will result in terminate in most of the cases.
What is the behavior on the absence of exception on the functions that throw exceptions? Are these functions disabled? Does the fucntion terminate? Calls a handler? All exceptions Outcome ever throws are done via a user redefinable macro. Those macros are listed at https://ned14.github.io/boost.outcome/md_doc_md_04-tutorial_c.html.
If C++ exceptions are enabled, the default macro definition throws the exception.
If C++ exceptions are disabled, the default macro definition prints a descriptive message and a stacktrace to stderr and calls std::terminate(). This is similar to what BOOST_THROW_EXCEPTION does, isn't it? Why re-invent the wheel? End users can define the Outcome exception throwing macros to call BOOST_THROW_EXCEPTION if they wish. For standalone Outcome users, the default behaviour is minimal, but sufficient.
Boost users should have already a default for BOOST_THROW_EXCEPTION and shouldn't need to add any additional customization for what to do when exceptions are not enabled. I don't agreed with the change on BOOST_THROW_EXCEPTION done by Emil, but the reality is that it is there now. Users of Boost should not customize each library, when there is already a common mechanism.
is an excellent neighbour to all other C++ libraries as for example? It pollutes no namespaces, interferes in no way with other C++ including other versions of itself. You can mix multiple versions of Outcome in the same binary safely. There are several version of Outcome? Currently every time I commit to the git repo there is a new version of Outcome. The ABI is permuted with the SHA of the git. This prevents unstable versions of Outcome conflicting with different unstable versions of Outcome in the same process.
Hugh. I don't see how would someone like to use two unstable version of the same library.
I will, at some future point, declare a stable ABI and pin it forever to v1 of the ABI and have that checked per commit by Travis.
I don't see any added value to this versioning. Up to you.
AND build systems, I'm less concerned by the build systems, but could you elaborate? Outcome doesn't require any magic macros predefined and can be used by end users simply by dropping a tarball of the library into their source code and getting to work. While this could be a quality for a standalone library, this is not a quality for a Boost library. The other C++ 14 library in Boost does not require any of Boost either.
Oh, yeah I forgotten, Boost.Outcome is a C++14 library and so it cannot use Boost and reinvent the wheel ;-)
(And my thanks to Andrzej for the motivating example on the landing page of the docs, he did a great job capturing the universal error handling framework aspect of Outcome. I would love to know if reviewers can make sense of it, or did it just confuse the hell out of everybody)
Could you tell us which example? It's the landing page motivating code example. Bottom of https://ned14.github.io/boost.outcome/index.html.
No this example doesn't show any advantage of Boost.Outcome, quite the opposite. However the other example that Andrzej has posted recently is a better example as I have already said as a response. The only difference was returning result instead of outcome, and removing the exception throwing function call. Otherwise the examples are identical.
The return value is very important; the example show an important thing of Boost.Outcome: error propagation. Vicente

Compilers spotted the common outcome::value_storage during moves when it was identical, and totally eliminated any memory copies. Okay, so the single thing outcome::outcome provides is the conversion from the other Outcomes. I believe this is great and I will add it to the open question for std::expected as I've already notified privately to you (waiting a response :) )
Yeah sorry, private email has backlogged. I'm already spending every free hour replying to email as it is. I am also mentoring a GSoC student too, he takes priority. But I'll get to your private email as soon as I can.
clang 4.0+ and GCC 6.0+ with -O3 optimisation appear to now be clever enough to spot trivial move constructors and trivial types, and not copy memory when the bits in memory would not change.
Do you mean that now we can use optional and expected without any performance penalty and that the design of Outcome is not needed any more?
This is correct. But to be specific, the **storage layout** design of Outcome is not needed any more with very recent compilers. I don't think the current approach is damaging though.
Currently every time I commit to the git repo there is a new version of Outcome. The ABI is permuted with the SHA of the git. This prevents unstable versions of Outcome conflicting with different unstable versions of Outcome in the same process.
Hugh. I don't see how would someone like to use two unstable version of the same library.
In large orgs, one team may use one version of a library and another team another version. Each team does not realise that the other team does this. When someone else combines the libraries into a single executable, boom!
No this example doesn't show any advantage of Boost.Outcome, quite the opposite. However the other example that Andrzej has posted recently is a better example as I have already said as a response. The only difference was returning result instead of outcome, and removing the exception throwing function call. Otherwise the examples are identical.
The return value is very important; the example show an important thing of Boost.Outcome: error propagation.
But we were propagating exception_ptr before. That is also error propagation. I don't get the difference. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Compilers spotted the common outcome::value_storage during moves when it was identical, and totally eliminated any memory copies. Okay, so the single thing outcome::outcome provides is the conversion from the other Outcomes. I believe this is great and I will add it to the open question for std::expected
clang 4.0+ and GCC 6.0+ with -O3 optimisation appear to now be clever enough to spot trivial move constructors and trivial types, and not copy memory when the bits in memory would not change. Do you mean that now we can use optional and expected without any performance penalty and that the design of Outcome is not needed any more? This is correct. But to be specific, the **storage layout** design of Outcome is not needed any more with very recent compilers. I don't think the current approach is damaging though. Up to you. KISS as much as possible.
Currently every time I commit to the git repo there is a new version of Outcome. The ABI is permuted with the SHA of the git. This prevents unstable versions of Outcome conflicting with different unstable versions of Outcome in the same process. Hugh. I don't see how would someone like to use two unstable version of the same library. In large orgs, one team may use one version of a library and another team another version. Each team does not realise that the other team does this. When someone else combines the libraries into a single executable, boom! I work on large org and each time we take a 3pp library we master it at
Le 24/05/2017 à 22:41, Niall Douglas via Boost a écrit : the project level. There is no chance we can use different versions of the same library and even less to use unstable versions. Others could need what you offer, but I believe it is too much.
No this example doesn't show any advantage of Boost.Outcome, quite the opposite. However the other example that Andrzej has posted recently is a better example as I have already said as a response. The only difference was returning result instead of outcome, and removing the exception throwing function call. Otherwise the examples are identical.
The return value is very important; the example show an important thing of Boost.Outcome: error propagation. But we were propagating exception_ptr before. That is also error propagation. I don't get the difference.
I believe the tutorial should show how errors are propagated using outcome classes in a transparent way, not showing how we can propagating exceptions by hand. Vicente

-----Original Message----- From: Boost [mailto:boost-bounces@lists.boost.org] On Behalf Of Niall Douglas via Boost Sent: 24 May 2017 16:21 To: boost@lists.boost.org Cc: Niall Douglas Subject: Re: [boost] Boost.Outcome review - First questions
clang 4.0+ and GCC 6.0+ with -O3 optimisation appear to now be clever enough to spot trivial move constructors and trivial types, and not copy memory when the bits in memory would not change.
I would therefore expect that your reference Expected implementation would now be identical or very similar in runtime performance to Outcome on clang 4.0+ and GCC 6.0+. MSVC does, of course, still have some way to go yet, but VS2017 is a world of improvement over VS2015.
For this review, I doubt if we should worry too much about *current released* compiler's performance. If Outcome becomes popular, we can expect compiler optimisers to focus their attention on optimising Outcome. Paul --- Paul A. Bristow Prizet Farmhouse Kendal UK LA8 8AB +44 (0) 1539 561830

Paul wrote:
For this review, I doubt if we should worry too much about *current released* compiler's performance. If Outcome becomes popular, we can expect compiler optimisers to focus their attention on optimising Outcome.
Outcome only targets very recent compilers because of performance, doesn't it? i.e. One of the reasons cited in another mail for dropping MSVC2015 (i.e. the second newest MSVC version) in favor of only supporting MSVC2017 is that the former generates suboptimal code for this Outcome implementation. Did I misunderstand that? i.e. If you're talking about the next major compiler version release from this particular vendor, what motivation is there to optimize for usage of Outcome, when they will probably already provide their own optimal expected<T, E> implementation? At that point, would there be a need for Outcome for those users who are just after an expected<T, E> implementation, and are already going to upgrade to a standard library implementation that provides a more optimal std::expected? I would have thoguht current compiler performance is important for that reason. Glen -- View this message in context: http://boost.2283326.n4.nabble.com/Boost-Outcome-review-First-questions-tp46... Sent from the Boost - Dev mailing list archive at Nabble.com.

Now to the reply to your post point by point ...
# Our |value_or()| member functions pass through the reference rather than returning by value (we don't understand why the LEWG proposal does except that |std::optional<T>| does, which itself is a bad design choice and it should be changed).
Well, what is the advantage to returning by reference? You don't cause .value_or() to surprisingly fail to compile when T lacks a move or copy constructor. The reference returning edition does not impose the requirement for T to have a move or copy constructor. This choice for .value_or() interferes less on end users. Do you have cases where expected<T,E> has a T that is not move/copy constructible? How can you return it?
You can't directly. But some might wrap it in a unique_ptr. Up to them, it is they who supply type T and they may not even control its implementation. If a user is forced to use such a T they cannot modify, I don't think you should punish them for that. In general I don't think you can rightly impose that T needs to have a move or copy constructor. You don't *have* to impose it, so don't impose it. Don't mess up the end user's life and make their programming harder for no good reason.
The fact optional implemented .value_or() with a return by value is a mistake. Expected need not repeat that mistake. Could you point me to the C++ standard std::optional defect? Have you created one?
optional is in the C++ 17 standard. Code will be written against its current design of .value_or(). It can no longer be changed :(
* it is not clear from the reference documentation if expected<void,E> is valid? Do you mean there is no reference in the API docs to an expected<void, E> specialisation? I didn't add one as I figured it was self evident and unsurprising. Also, if we document expected<void, E>, we'd have to also do result<void>, outcome<void>, expected<void, void> etc and to be honest, they all behave exactly as you'd expect a void typed edition would: wherever there is a T, you get T=void. Nothing special. Maybe or maybe not. What is the signature of value_or?
Exactly the same: value_type &value_or(value_type &) value_type is defined to an unusable but helpfully named type for compiler warnings and error when raw_value_type=void. This is very tersely explained in the Expected synopsis page. I will be improving the reference API docs to describe this better as part of issue #26.
|* |is expected<T,E> the sum type of T, E and empty? and exception_ptr? expected<T, E> may only have the state of T or E. The valueless by exception state has been eliminated recently in develop branch. Is this the case also for outcome/result/option?
Yes.
What are the exception warranties for |error_code_extended? It never throws, not ever.
This is pretty clear in the reference docs. Everything is marked noexcept. I don't see it in https://ned14.github.io/boost.outcome/classboost_1_1outcome_1_1v1__xxx_1_1er... Could you point me where noexcept is?
Heh. You are completely right, noexcept is missing. That's a bug: logged to https://github.com/ned14/boost.outcome/issues/34.
What are the exception warranties for |outcome<T>||? The tutorial covers those. The reference docs will too when issue #26 is implemented. The noexcept modifiers on most of the member functions are pretty clear however. This is the kind of information we want on the reference documentation. Could you tell us what the issue #26 The default actions for each of .value(), .error(), .exception() etc need to be spelled out in the reference docs <https://github.com/ned14/boost.outcome/issues/26> will add to the docs and how this issue is related? What about the other functions, constructors, assignments ...
The information in the tutorial will be copied into each reference API's docs using a style similar to standardese. I will cover constructors, assignments, destructors etc as well.
You are correct, but I left it unfixed to remind me to ask here what would be a suitable unit test for testing an "initializer_list<U> il, Args&&... args" constructor where the Args&&... gets used correctly. I was unsure of the semantics. Take a look at std::optional test if you are curious.
I found only this in libstdc++'s unit tests for its optional: std::optional<std::vector<int>> o { std::in_place, { 18, 4 }, std::allocator<int> {} }; It'll do.
Why do you prefer an implicit conversion. This deviation must be in the rationale of the differences. The deviations list already explains that types T and E cannot be convertible into one another because we use simple overloading to avoid using SFINAE to keep compile time load low.
Code written for LEWG Expected will, unless types T and E are convertible into one another, work as expected. You don't answer to my question. Why implicit? What is wrong with the constructor
expected(unexpect_t, error)
?
As I've mentioned to you by private email, a lot of end users want implicit conversion from T and E to save on boilerplate. Outcome doesn't allow T and E to be convertible into one another, so permitting implicit conversion from both T and E is safe. Consider it a populist extension over LEWG Expected. I know you don't like it, and I to be honest think it best used sparingly, but it'll be very popular with many.
* raw types
It is not clear what is the difference between xxx and raw_xxx Logged to https://github.com/ned14/boost.outcome/issues/29 Thanks for creating the issue, but I'm interested in knowing it now during the review. Otherwise I couldn't accept even conditionally the library.
When configured with void, value_type, error_type and exception_type are all set to an unusable but usefully named types suitable for compiler warnings and errors. This makes writing metaprogramming much easier as you don't need to deal specially with void and its weird semantics, plus any attempt to use the type causes a very descriptive compiler error. raw_value_type, raw_error_type and raw_exception_type are the true, original type configured. You are correct that this is a deviation from LEWG Expected which would cause code written for LEWG Expected to fail to compile with Outcome's Expected. I think that safe.
* what is the sizeof expected <T,E>? It says already at the top of the page. It's max(24, sizeof(R)+8) on 64 bit CPUs. An now that you will ensure the never empty warranties?
It's the same sizeof. If all your types have nothrow move, or your types don't have move nor copy constructors, we directly destruct and construct during assignment without saving the old state as the fast path. Else we move the current state onto a stack based copy before copy or move constructing in the new state. If that throws, we move back the previous state. If that restoring move throws as well, we are left valueless by exception/empty. I didn't implement Anthony William's double buffer solution for perfect never empty warranties even when the types have no move nor copy constructors. It is quite complex if you also wish to get alignment right, constexpr right, and keep the optimiser maximally folding code. I will document the semantics described above in the tutorial and reference API docs. They are a good balance of never empty warranties with space consumption and runtime overhead. I believe these guarantees are similar to those in your Expected proposal? As in, you don't implement the double buffer solution either.
The return by E rather than expected<void, E> is by design. Remember we permit implicit construction from E, so this is safe and works well. Again, code written for LEWG Expected should never notice this implementation detail. Why? if the signatures doesn't match I will have sure a problem somewhere.
Such problems are on me as the maintainer.
(the real cause for this deviation is because the policy classes cannot return types still being fabricated, so they cannot return an expected<void, E>. If this deviation ever should become a problem, I can relocate get_unexpected to the main class and enable_if it, or else use a trampoline type. But I believe the existing form replicates the same semantics, it should be fine) This is what I don't like of your library. In order to have a common implementation you sacrifice the concrete interfaces.
The common implementation is the heart and soul of Outcome, and is precisely its "value add" over most other Either monad or Expected implementations. A common implementation allows seamless interoperation between implementations. It lets you write code using expected<T, E> with outcome<T>, result<T> and option<T> all using the exact same framework. End users can extend the family with any arbitrary custom error transport of their choosing for some bespoke use case.
I prefer to have concrete classes that provide the interface is the most adapted to these classes and then have an adaptation to some type of classes requirement when I need to do generic code.
In most of my libraries so do I. But in this situation, I went a different direction. I think it's the right one for this use case.
This something that I remarked since the beginning of your library. The basic_monad class is over-engineered IMHO.
Please see the other email for a reply to this sentiment.
Vicente thanks very much for such detailed feedback. It was very valuable.
Niall, please, don't wait until the review is finished to tell us how the issues will be fixed. You could do it on each issue and come back in this ML.
I try my best to do so. But a lot of time, how the issues will be fixed is very obvious: I copy and paste documentation from one location to another for example, or I repair an incorrect constructor so it is correct and so on. It's not rocket science to deduce how most issues will be fixed. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Now to the reply to your post point by point ...
# Our |value_or()| member functions pass through the reference rather than returning by value (we don't understand why the LEWG proposal does except that |std::optional<T>| does, which itself is a bad design choice and it should be changed).
Well, what is the advantage to returning by reference? You don't cause .value_or() to surprisingly fail to compile when T lacks a move or copy constructor. The reference returning edition does not impose the requirement for T to have a move or copy constructor. This choice for .value_or() interferes less on end users. Do you have cases where expected<T,E> has a T that is not move/copy constructible? How can you return it? You can't directly. But some might wrap it in a unique_ptr. Up to them, it is they who supply type T and they may not even control its implementation. If a user is forced to use such a T they cannot modify, I don't think you should punish them for that.
The fact optional implemented .value_or() with a return by value is a mistake. Expected need not repeat that mistake. Could you point me to the C++ standard std::optional defect? Have you created one?
In general I don't think you can rightly impose that T needs to have a move or copy constructor. You don't *have* to impose it, so don't impose it. Don't mess up the end user's life and make their programming harder for no good reason. Niall, I will not be against your interface if I had a use case or if it can perform better. Providing a more complex interface because we can and in case someone can need it is not enough IMHO. optional is in the C++ 17 standard. Code will be written against its current design of .value_or(). It can no longer be changed :( You found this 2 years ago, isn't it?
* it is not clear from the reference documentation if expected<void,E> is valid? Do you mean there is no reference in the API docs to an expected<void, E> specialisation? I didn't add one as I figured it was self evident and unsurprising. Also, if we document expected<void, E>, we'd have to also do result<void>, outcome<void>, expected<void, void> etc and to be honest, they all behave exactly as you'd expect a void typed edition would: wherever there is a T, you get T=void. Nothing special. Maybe or maybe not. What is the signature of value_or? Exactly the same: value_type &value_or(value_type &) I believe these differences merit to be in the reference documentation.
value_type is defined to an unusable but helpfully named type for compiler warnings and error when raw_value_type=void. This is very tersely explained in the Expected synopsis page. I will be improving the reference API docs to describe this better as part of issue #26.
|* |is expected<T,E> the sum type of T, E and empty? and exception_ptr? expected<T, E> may only have the state of T or E. The valueless by exception state has been eliminated recently in develop branch. Is this the case also for outcome/result/option? Yes. Great, so result<T> and option<T> wouldn't have no anymore a tri-logic, and outcome<T> would have a tri-logic as it can be T | E | exception_ptr, but never empty. I'm not sure, but I believe you maybe will need to double the storage. Have you implemented this already?
What are the exception warranties for |error_code_extended? It never throws, not ever.
This is pretty clear in the reference docs. Everything is marked noexcept. I don't see it in https://ned14.github.io/boost.outcome/classboost_1_1outcome_1_1v1__xxx_1_1er... Could you point me where noexcept is? Heh. You are completely right, noexcept is missing. That's a bug: logged to https://github.com/ned14/boost.outcome/issues/34.
What are the exception warranties for |outcome<T>||? The tutorial covers those. The reference docs will too when issue #26 is implemented. The noexcept modifiers on most of the member functions are pretty clear however. This is the kind of information we want on the reference documentation. Could you tell us what the issue #26 The default actions for each of .value(), .error(), .exception() etc need to be spelled out in the reference docs <https://github.com/ned14/boost.outcome/issues/26> will add to the docs and how this issue is related? What about the other functions, constructors, assignments ... The information in the tutorial will be copied into each reference API's docs using a style similar to standardese. I will cover constructors, assignments, destructors etc as well.
You are correct, but I left it unfixed to remind me to ask here what would be a suitable unit test for testing an "initializer_list<U> il, Args&&... args" constructor where the Args&&... gets used correctly. I was unsure of the semantics. Take a look at std::optional test if you are curious. I found only this in libstdc++'s unit tests for its optional:
std::optional<std::vector<int>> o { std::in_place, { 18, 4 }, std::allocator<int> {} };
It'll do.
Why do you prefer an implicit conversion. This deviation must be in the rationale of the differences. The deviations list already explains that types T and E cannot be convertible into one another because we use simple overloading to avoid using SFINAE to keep compile time load low.
Code written for LEWG Expected will, unless types T and E are convertible into one another, work as expected. You don't answer to my question. Why implicit? What is wrong with the constructor
expected(unexpect_t, error)
? As I've mentioned to you by private email, a lot of end users want implicit conversion from T and E to save on boilerplate. Outcome doesn't allow T and E to be convertible into one another, so permitting implicit conversion from both T and E is safe. Aside what we can have discussed privately, we are here reviewing the
Le 22/05/2017 à 01:15, Niall Douglas via Boost a écrit : library. I believe this kind of information should be part of the rationale. I would say the limitation you impose to T and E permit to define the constructor implicitly. This doesn't mean that we need to.
Consider it a populist extension over LEWG Expected. I know you don't like it, and I to be honest think it best used sparingly, but it'll be very popular with many.
Popular is not a criteria I will run after.
* raw types
It is not clear what is the difference between xxx and raw_xxx Logged to https://github.com/ned14/boost.outcome/issues/29 Thanks for creating the issue, but I'm interested in knowing it now during the review. Otherwise I couldn't accept even conditionally the library. When configured with void, value_type, error_type and exception_type are all set to an unusable but usefully named types suitable for compiler warnings and errors. This makes writing metaprogramming much easier as you don't need to deal specially with void and its weird semantics, plus any attempt to use the type causes a very descriptive compiler error.
raw_value_type, raw_error_type and raw_exception_type are the true, original type configured. You are correct that this is a deviation from LEWG Expected which would cause code written for LEWG Expected to fail to compile with Outcome's Expected. I think that safe.
If the raw_ are the reason d'être to avoid the void specialization, and this avers to be a useful technique, I believe it merits a full implementation section explaining how this improve the compiler performances, DRY, et all. I'm not against something I don't understand, in principle, just want to understand why you did the way you did and if it is a good technique adopt it on my proposals.
* what is the sizeof expected <T,E>? It says already at the top of the page. It's max(24, sizeof(R)+8) on 64 bit CPUs. An now that you will ensure the never empty warranties? It's the same sizeof.
What are the magic number 24 and 8? I'm sure I can undertand it better if I read the implementation, but I don't I want to read the documentation and understand it.
If all your types have nothrow move, or your types don't have move nor copy constructors, we directly destruct and construct during assignment without saving the old state as the fast path. Else we move the current state onto a stack based copy before copy or move constructing in the new state. If that throws, we move back the previous state. If that restoring move throws as well, we are left valueless by exception/empty.
I didn't implement Anthony William's double buffer solution for perfect never empty warranties even when the types have no move nor copy constructors. It is quite complex if you also wish to get alignment right, constexpr right, and keep the optimiser maximally folding code.
I will document the semantics described above in the tutorial and reference API docs. They are a good balance of never empty warranties with space consumption and runtime overhead. I believe these guarantees are similar to those in your Expected proposal? As in, you don't implement the double buffer solution either.
No, I don't need to. There is not need if E concerned function not throw, which I could expect as E is an error.
The return by E rather than expected<void, E> is by design. Remember we permit implicit construction from E, so this is safe and works well. Again, code written for LEWG Expected should never notice this implementation detail. Why? if the signatures doesn't match I will have sure a problem somewhere. Such problems are on me as the maintainer.
No the possible problem will appear on the user side as soon as the user make use of the signatures. I agree that this can be a corner case.
(the real cause for this deviation is because the policy classes cannot return types still being fabricated, so they cannot return an expected<void, E>. If this deviation ever should become a problem, I can relocate get_unexpected to the main class and enable_if it, or else use a trampoline type. But I believe the existing form replicates the same semantics, it should be fine) This is what I don't like of your library. In order to have a common implementation you sacrifice the concrete interfaces. The common implementation is the heart and soul of Outcome, and is precisely its "value add" over most other Either monad or Expected implementations. A common implementation allows seamless interoperation between implementations. It lets you write code using expected<T, E> with outcome<T>, result<T> and option<T> all using the exact same framework. End users can extend the family with any arbitrary custom error transport of their choosing for some bespoke use case.
Your solution is intrusive IMHO. How boost::experimental::expected<T,E> will interact with std::experimental::expected<T,E> or std::optional<T>, or something else that behaves like a monad error? If we want different monad errors to interact between them we should build this on top of the monadic error interface. And IMO, we should do it explicitly as we do with exceptions when we transform some exceptions.
I prefer to have concrete classes that provide the interface is the most adapted to these classes and then have an adaptation to some type of classes requirement when I need to do generic code. In most of my libraries so do I. But in this situation, I went a different direction. I think it's the right one for this use case.
No problem. We just disagree here.
This something that I remarked since the beginning of your library. The basic_monad class is over-engineered IMHO. Please see the other email for a reply to this sentiment.
It is late now. I will replay to it this evening.
Vicente thanks very much for such detailed feedback. It was very valuable.
Niall, please, don't wait until the review is finished to tell us how the issues will be fixed. You could do it on each issue and come back in this ML. I try my best to do so. But a lot of time, how the issues will be fixed is very obvious: I copy and paste documentation from one location to another for example, or I repair an incorrect constructor so it is correct and so on. It's not rocket science to deduce how most issues will be fixed.
It could be obvious for you, but we need to review those possible changes before accepting the library. Niall, I'm not against not for you library. We're just reviewing it now. For me the goal is to improve it, and if we can at the same time improve the std expected proposal this will be very valuable to the C++ community. Best, Vicente

|* |is expected<T,E> the sum type of T, E and empty? and exception_ptr? expected<T, E> may only have the state of T or E. The valueless by exception state has been eliminated recently in develop branch. Is this the case also for outcome/result/option? Yes. Great, so result<T> and option<T> wouldn't have no anymore a tri-logic, and outcome<T> would have a tri-logic as it can be T | E | exception_ptr, but never empty.
Formal empty state remains for outcome<T>, result<T>, option<T>. As I've hopefully shown by now in other discussion threads, the formal empty state is very useful, saves a lot of boilerplate. For those not wanting the empty state, there is expected<T, E>.
I'm not sure, but I believe you maybe will need to double the storage. Have you implemented this already?
I explained before the exact implementation, and where valueless due to exception can still arise. It will be documented soon (this change was implemented during this review).
Consider it a populist extension over LEWG Expected. I know you don't like it, and I to be honest think it best used sparingly, but it'll be very popular with many. Popular is not a criteria I will run after.
I am, however, a populist, and that is evident throughout the design of Outcome and especially AFIO v2. I give the people want they want so long as it is not obviously dangerous nor stupid. I appreciate that is not a common design philosophy here, and especially not on WG21. But I think C++ could do with more feeding the masses rather than so much ivory tower design and forcing weird design, build and distribution systems onto end users.
* raw types
It is not clear what is the difference between xxx and raw_xxx Logged to https://github.com/ned14/boost.outcome/issues/29 Thanks for creating the issue, but I'm interested in knowing it now during the review. Otherwise I couldn't accept even conditionally the library. When configured with void, value_type, error_type and exception_type are all set to an unusable but usefully named types suitable for compiler warnings and errors. This makes writing metaprogramming much easier as you don't need to deal specially with void and its weird semantics, plus any attempt to use the type causes a very descriptive compiler error.
raw_value_type, raw_error_type and raw_exception_type are the true, original type configured. You are correct that this is a deviation from LEWG Expected which would cause code written for LEWG Expected to fail to compile with Outcome's Expected. I think that safe. If the raw_ are the reason d'être to avoid the void specialization, and this avers to be a useful technique, I believe it merits a full implementation section explaining how this improve the compiler performances, DRY, et all.
I had thought it a very common technique, so common that it not worth explaining here. Am I wrong on this?
I'm not against something I don't understand, in principle, just want to understand why you did the way you did and if it is a good technique adopt it on my proposals.
I suspect it would not be accepted into the STL. Disabling overloads via setting their type to something impossible to match is not something I've ever seen in any STL.
* what is the sizeof expected <T,E>? It says already at the top of the page. It's max(24, sizeof(R)+8) on 64 bit CPUs. An now that you will ensure the never empty warranties? It's the same sizeof. What are the magic number 24 and 8? I'm sure I can undertand it better if I read the implementation, but I don't I want to read the documentation and understand it.
Is the max of either 24 bytes OR sizeof(T) + 8 So on x64 the bookkeeping for a basic_monad (any implementation) is 8 bytes above the largest possible type stored in the variant storage which is the minimum possible. The presented library has a small space optimisation which can pack storage into one byte in some circumstances, but I'm soon to remove it entirely. It suffers the same problem as vector<bool>, so .value() and .error() suddenly return by value instead of by reference etc.
I will document the semantics described above in the tutorial and reference API docs. They are a good balance of never empty warranties with space consumption and runtime overhead. I believe these guarantees are similar to those in your Expected proposal? As in, you don't implement the double buffer solution either. No, I don't need to. There is not need if E concerned function not throw, which I could expect as E is an error.
Cool, then Outcome's Expected is now conforming to your proposal in this area.
This is what I don't like of your library. In order to have a common implementation you sacrifice the concrete interfaces. The common implementation is the heart and soul of Outcome, and is precisely its "value add" over most other Either monad or Expected implementations. A common implementation allows seamless interoperation between implementations. It lets you write code using expected<T, E> with outcome<T>, result<T> and option<T> all using the exact same framework. End users can extend the family with any arbitrary custom error transport of their choosing for some bespoke use case.
Your solution is intrusive IMHO.
Yes, that is fair.
How boost::experimental::expected<T,E> will interact with std::experimental::expected<T,E> or std::optional<T>, or something else that behaves like a monad error?
Did you mean boost::outcome::experimental::expected<T, E>? The answer is that we currently don't. The seamless interop applies to within Outcome's island of types only. However it is very straightforward to write converting constructors to construct the std::* editions into Outcome editions. Getting them back out is on the end user, though I could be persuaded to write an explicit conversion operator for the std::** editions. This sort of relationship is exactly the one you previously said is how you would have implemented each of outcome<T>, result<T>, option<T> and expected<T, E> in Outcome, so each has a totally standalone and unrelated implementation, and each provides custom conversion mechanics into the others. That would be the traditional way of implementing this. I chose not to follow that design in this particular case.
Niall, please, don't wait until the review is finished to tell us how the issues will be fixed. You could do it on each issue and come back in this ML. I try my best to do so. But a lot of time, how the issues will be fixed is very obvious: I copy and paste documentation from one location to another for example, or I repair an incorrect constructor so it is correct and so on. It's not rocket science to deduce how most issues will be fixed.
It could be obvious for you, but we need to review those possible changes before accepting the library.
I will try to do better. I was particularly lacking in time to do this review until yesterday because I had my final maths coursework to complete and submit. That went in yesterday. I should have a lot more free time not at 1am in the morning from now on. That should make for better quality replies here, I am not at my best so late at night.
Niall, I'm not against not for you library. We're just reviewing it now. For me the goal is to improve it, and if we can at the same time improve the std expected proposal this will be very valuable to the C++ community.
I would be very glad if this process helps the Expected proposal. Expected is a great proposal, it needs into the C++ standard. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Le 22/05/2017 à 22:33, Niall Douglas via Boost a écrit :
|* |is expected<T,E> the sum type of T, E and empty? and exception_ptr? expected<T, E> may only have the state of T or E. The valueless by exception state has been eliminated recently in develop branch. Is this the case also for outcome/result/option? Yes. Great, so result<T> and option<T> wouldn't have no anymore a tri-logic, and outcome<T> would have a tri-logic as it can be T | E | exception_ptr, but never empty. Formal empty state remains for outcome<T>, result<T>, option<T>. As I've hopefully shown by now in other discussion threads, the formal empty state is very useful, saves a lot of boilerplate. For those not wanting the empty state, there is expected<T, E>.
Okay, understood, even if I don't agree, at least for result<T> and option<T>. For outcome<T> I have my doubts, I don't know if the never-empty warranties can be implemented in an efficient way.
I'm not sure, but I believe you maybe will need to double the storage. Have you implemented this already? I explained before the exact implementation, and where valueless due to exception can still arise. It will be documented soon (this change was implemented during this review).
Okay, I don't see yet if you could ensure non-empty warranties for outcome.
Consider it a populist extension over LEWG Expected. I know you don't like it, and I to be honest think it best used sparingly, but it'll be very popular with many. Popular is not a criteria I will run after. I am, however, a populist, and that is evident throughout the design of Outcome and especially AFIO v2. I give the people want they want so long as it is not obviously dangerous nor stupid.
I appreciate that is not a common design philosophy here, and especially not on WG21. But I think C++ could do with more feeding the masses rather than so much ivory tower design and forcing weird design, build and distribution systems onto end users.
I appreciate your popular words "masses", "ivory tower" and "weird design". You know well how and when to use them.
* raw types
It is not clear what is the difference between xxx and raw_xxx Logged to https://github.com/ned14/boost.outcome/issues/29 Thanks for creating the issue, but I'm interested in knowing it now during the review. Otherwise I couldn't accept even conditionally the library. When configured with void, value_type, error_type and exception_type are all set to an unusable but usefully named types suitable for compiler warnings and errors. This makes writing metaprogramming much easier as you don't need to deal specially with void and its weird semantics, plus any attempt to use the type causes a very descriptive compiler error.
raw_value_type, raw_error_type and raw_exception_type are the true, original type configured. You are correct that this is a deviation from LEWG Expected which would cause code written for LEWG Expected to fail to compile with Outcome's Expected. I think that safe. If the raw_ are the reason d'être to avoid the void specialization, and this avers to be a useful technique, I believe it merits a full implementation section explaining how this improve the compiler performances, DRY, et all. I had thought it a very common technique, so common that it not worth explaining here.
Maybe you can point me to some blog, mail exchange, ...
Am I wrong on this?
Possibly. What is clear to you is is not forcedly to other.
I'm not against something I don't understand, in principle, just want to understand why you did the way you did and if it is a good technique adopt it on my proposals. I suspect it would not be accepted into the STL. Disabling overloads via setting their type to something impossible to match is not something I've ever seen in any STL.
I don't know what you are talking of, but I will really be interested in an explanation of the mechanism. There is always something new on the STL if the author reaches to have consensus. Evidently the standard couldn't include what each one could consider as a best solution. We need a relative majority and not too much disagreement.
* what is the sizeof expected <T,E>? It says already at the top of the page. It's max(24, sizeof(R)+8) on 64 bit CPUs. An now that you will ensure the never empty warranties? It's the same sizeof. What are the magic number 24 and 8? I'm sure I can undertand it better if I read the implementation, but I don't I want to read the documentation and understand it. Is the max of either 24 bytes OR sizeof(T) + 8
So on x64 the bookkeeping for a basic_monad (any implementation) is 8 bytes above the largest possible type stored in the variant storage which is the minimum possible. The presented library has a small space optimisation which can pack storage into one byte in some circumstances, but I'm soon to remove it entirely. It suffers the same problem as vector<bool>, so .value() and .error() suddenly return by value instead of by reference etc.
Where E is stored? What happens if sizeof(E)>sizeof(T)? I don't know yet what 24 and 8 stands for. Are 8 bytes use for the boolean?
I will document the semantics described above in the tutorial and reference API docs. They are a good balance of never empty warranties with space consumption and runtime overhead. I believe these guarantees are similar to those in your Expected proposal? As in, you don't implement the double buffer solution either. No, I don't need to. There is not need if E concerned function not throw, which I could expect as E is an error. Cool, then Outcome's Expected is now conforming to your proposal in this area.
This is what I don't like of your library. In order to have a common implementation you sacrifice the concrete interfaces. The common implementation is the heart and soul of Outcome, and is precisely its "value add" over most other Either monad or Expected implementations. A common implementation allows seamless interoperation between implementations. It lets you write code using expected<T, E> with outcome<T>, result<T> and option<T> all using the exact same framework. End users can extend the family with any arbitrary custom error transport of their choosing for some bespoke use case. Your solution is intrusive IMHO. Yes, that is fair.
How boost::experimental::expected<T,E> will interact with std::experimental::expected<T,E> or std::optional<T>, or something else that behaves like a monad error? Did you mean boost::outcome::experimental::expected<T, E>?
No evidently.
The answer is that we currently don't. The seamless interop applies to within Outcome's island of types only. However it is very straightforward to write converting constructors to construct the std::* editions into Outcome editions. Getting them back out is on the end user, though I could be persuaded to write an explicit conversion operator for the std::** editions.
So if the interaction is 3pp libraries is by defining conversion operators, why do we need Outcome. We could define already those operators without making the design more complex.
This sort of relationship is exactly the one you previously said is how you would have implemented each of outcome<T>, result<T>, option<T> and expected<T, E> in Outcome, so each has a totally standalone and unrelated implementation, and each provides custom conversion mechanics into the others. I would do it as std::optional, std::expected are done now. If there is a common implementation, this is an implementation detail. Note that at the end all these types are sum types and as far as we have a never-empty variant implementation we could share a lot of code between these concrete classes. I haven't a never-empty warranty xxx::variant at hand, but once we have it the optional and expected implementation are almost evident. Conversion between monadic types is concern I have not study deeply, but in any case I will not use an intrusive approach as yours. I may be wrong, but each one should live with its incoherencies (coherency).
For example I'm proposing a ProductType concept to the standard. Each product type could define when it converts from any ProductType satisfying certain properties. This will simplify and extend the current tuple-like interfaces with less code and more software. I believe we could define a PossiblyValued type of classes and define conversion from them in a non intrusive way. Note that I used PossiblyValued and not MonadError as the required interfaces wil not be the same. A PossiblyValued value type that is close to Nullable, but where the not-a-value is considered an error and can have multiple instances. optional and expected and any the smart pointer I know will be PossiblyValued types. Let me know if you consider this design on the ivory tower and a weird design.
That would be the traditional way of implementing this. I chose not to follow that design in this particular case.
I've no problem as far as the common implementation is hidden and doesn't guide the concrete interface. I believe that generic programming must be built on top od concepts, not inside a intrusive class that know how to do everything.
Niall, please, don't wait until the review is finished to tell us how the issues will be fixed. You could do it on each issue and come back in this ML. I try my best to do so. But a lot of time, how the issues will be fixed is very obvious: I copy and paste documentation from one location to another for example, or I repair an incorrect constructor so it is correct and so on. It's not rocket science to deduce how most issues will be fixed.
It could be obvious for you, but we need to review those possible changes before accepting the library. I will try to do better. I was particularly lacking in time to do this review until yesterday because I had my final maths coursework to complete and submit. That went in yesterday. I should have a lot more free time not at 1am in the morning from now on. That should make for better quality replies here, I am not at my best so late at night.
No problem, take your time.
Niall, I'm not against not for you library. We're just reviewing it now. For me the goal is to improve it, and if we can at the same time improve the std expected proposal this will be very valuable to the C++ community. I would be very glad if this process helps the Expected proposal. Expected is a great proposal, it needs into the C++ standard.
It is weird, that you say that it is a great proposal, but you found something that must be added, modified or removed. I would like to understand these differences, because surely the expected design must be improved when I would take into account *valid* use cases that I have not considered yet. Best, Vicente

Formal empty state remains for outcome<T>, result<T>, option<T>. As I've hopefully shown by now in other discussion threads, the formal empty state is very useful, saves a lot of boilerplate. For those not wanting the empty state, there is expected<T, E>.
Okay, understood, even if I don't agree, at least for result<T> and option<T>. For outcome<T> I have my doubts, I don't know if the never-empty warranties can be implemented in an efficient way.
Why do you think outcome<T> cannot implement a never empty warranty? Perhaps you are thinking of std::exception_ptr's very unfortunate lack of exception warranties on its move and copy constructors? I agree that the lack of definition by the C++ standard there is deeply unhelpful. Older STLs also didn't implement a move constructor because the standard doesn't require one, and marked the copy constructor as throwing. Very unhelpful. But recent STLs appear to have since added a nothrow move constructor. I think we are safe-in-practice rather than safe-through-design.
raw_value_type, raw_error_type and raw_exception_type are the true, original type configured. You are correct that this is a deviation from LEWG Expected which would cause code written for LEWG Expected to fail to compile with Outcome's Expected. I think that safe. If the raw_ are the reason d'être to avoid the void specialization, and this avers to be a useful technique, I believe it merits a full implementation section explaining how this improve the compiler performances, DRY, et all. I had thought it a very common technique, so common that it not worth explaining here. Maybe you can point me to some blog, mail exchange, ...
Am I wrong on this? Possibly. What is clear to you is is not forcedly to other.
Ok, people please tell me if this technique is unknown to them: template<class A, class B, class C> class Something { class A_construction_disabled // note this is in private: { A_construction_disabled() = delete; }; using enable_A_construction = typename std::conditional<std::is_void<A>::value, A_construction_disabled, A>::type; public: // Implicit constructor from A, if not void Something(enable_A_construction &&a) ... ... }; This lets you avoid enable_if testing for void on every single member function able to consume a type A. Surely this is a widely used and well understood metaprogramming technique?
The answer is that we currently don't. The seamless interop applies to within Outcome's island of types only. However it is very straightforward to write converting constructors to construct the std::* editions into Outcome editions. Getting them back out is on the end user, though I could be persuaded to write an explicit conversion operator for the std::** editions.
So if the interaction is 3pp libraries is by defining conversion operators, why do we need Outcome. We could define already those operators without making the design more complex.
The intention is to provide interaction with *STL* supplied alternatives via this means. That's because we can't adjust the source code of STL types. Third party code where the author may modify the source should supply a custom policy class to basic_monad.
For example I'm proposing a ProductType concept to the standard. Each product type could define when it converts from any ProductType satisfying certain properties. This will simplify and extend the current tuple-like interfaces with less code and more software.
I believe we could define a PossiblyValued type of classes and define conversion from them in a non intrusive way. Note that I used PossiblyValued and not MonadError as the required interfaces wil not be the same. A PossiblyValued value type that is close to Nullable, but where the not-a-value is considered an error and can have multiple instances. optional and expected and any the smart pointer I know will be PossiblyValued types.
Let me know if you consider this design on the ivory tower and a weird design.
I think this is all great stuff for pondering for future C++ standard libraries. But not strictly relevant to this Outcome review.
That would be the traditional way of implementing this. I chose not to follow that design in this particular case.
I've no problem as far as the common implementation is hidden and doesn't guide the concrete interface. I believe that generic programming must be built on top od concepts, not inside a intrusive class that know how to do everything.
I think you may not have studied the source code sufficiently. basic_monad definitely does not know how to do everything. basic_monad takes a policy class to flavour it with personality. The policy class is a type full of typedefs and constants setting up what this particular basic_monad will do. The storage layout is strictly separately defined by policy than the public member functions mixed in by the policy to the final basic_monad class instantiated. So, you could use a std::variant for the storage layout, or three std::optional<>'s, or three unique_ptr<>'s, or more excitedly, a std::expected<T, E>! Up to the definer. The public member functions provided by this particular basic_monad are customisable by a separate sub-policy policy. basic_monad itself provides all the public member functions guaranteed to be common across all basic_monad varieties, though it can and does call into the policy class for implementation. This common interface allows a common machinery to be built on top, so specifically the implicit conversion from less to more representative, and the TRY operation. So yes the above is intrusive, it forces an outcome-y thing to always be made. But how you implement it is up to you, so long as you fulfil the Outcome public contract.
Niall, I'm not against not for you library. We're just reviewing it now. For me the goal is to improve it, and if we can at the same time improve the std expected proposal this will be very valuable to the C++ community. I would be very glad if this process helps the Expected proposal. Expected is a great proposal, it needs into the C++ standard.
It is weird, that you say that it is a great proposal, but you found something that must be added, modified or removed. I would like to understand these differences, because surely the expected design must be improved when I would take into account *valid* use cases that I have not considered yet.
I nitpick tiny corner case problems, but I consider the overall proposal to be great. I do thank you personally in Outcome's Acknowledgement for all your service in making Expected happen, without all your work Outcome would not exist and my other C++ libraries using Outcome would be the worse for it. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Le 23/05/2017 à 17:04, Niall Douglas via Boost a écrit :
Formal empty state remains for outcome<T>, result<T>, option<T>. As I've hopefully shown by now in other discussion threads, the formal empty state is very useful, saves a lot of boilerplate. For those not wanting the empty state, there is expected<T, E>. Okay, understood, even if I don't agree, at least for result<T> and option<T>. For outcome<T> I have my doubts, I don't know if the never-empty warranties can be implemented in an efficient way. Why do you think outcome<T> cannot implement a never empty warranty?
Perhaps you are thinking of std::exception_ptr's very unfortunate lack of exception warranties on its move and copy constructors? Exactly.
I agree that the lack of definition by the C++ standard there is deeply unhelpful. Older STLs also didn't implement a move constructor because the standard doesn't require one, and marked the copy constructor as throwing. Very unhelpful.
But recent STLs appear to have since added a nothrow move constructor. I think we are safe-in-practice rather than safe-through-design. We need a change on the standard to ensure that.
raw_value_type, raw_error_type and raw_exception_type are the true, original type configured. You are correct that this is a deviation from LEWG Expected which would cause code written for LEWG Expected to fail to compile with Outcome's Expected. I think that safe. If the raw_ are the reason d'être to avoid the void specialization, and this avers to be a useful technique, I believe it merits a full implementation section explaining how this improve the compiler performances, DRY, et all. I had thought it a very common technique, so common that it not worth explaining here. Maybe you can point me to some blog, mail exchange, ... Am I wrong on this? Possibly. What is clear to you is is not forcedly to other. Ok, people please tell me if this technique is unknown to them:
template<class A, class B, class C> class Something { class A_construction_disabled // note this is in private: { A_construction_disabled() = delete; }; using enable_A_construction = typename std::conditional<std::is_void<A>::value, A_construction_disabled, A>::type;
public: // Implicit constructor from A, if not void Something(enable_A_construction &&a) ... ... };
This lets you avoid enable_if testing for void on every single member function able to consume a type A. Surely this is a widely used and well understood metaprogramming technique?
Thanks for explaining. Does this provide a constructor without argument, when A is void?? Something() ...
The answer is that we currently don't. The seamless interop applies to within Outcome's island of types only. However it is very straightforward to write converting constructors to construct the std::* editions into Outcome editions. Getting them back out is on the end user, though I could be persuaded to write an explicit conversion operator for the std::** editions. So if the interaction is 3pp libraries is by defining conversion operators, why do we need Outcome. We could define already those operators without making the design more complex. The intention is to provide interaction with *STL* supplied alternatives via this means. That's because we can't adjust the source code of STL types.
Except if we write a proposal ;-)
Third party code where the author may modify the source should supply a custom policy class to basic_monad.
No, I don't think creating a new class using outcome is a solution. If the 3pp has already one the user must be forced to use it.
For example I'm proposing a ProductType concept to the standard. Each product type could define when it converts from any ProductType satisfying certain properties. This will simplify and extend the current tuple-like interfaces with less code and more software.
I believe we could define a PossiblyValued type of classes and define conversion from them in a non intrusive way. Note that I used PossiblyValued and not MonadError as the required interfaces wil not be the same. A PossiblyValued value type that is close to Nullable, but where the not-a-value is considered an error and can have multiple instances. optional and expected and any the smart pointer I know will be PossiblyValued types.
Let me know if you consider this design on the ivory tower and a weird design. I think this is all great stuff for pondering for future C++ standard libraries. But not strictly relevant to this Outcome review.
It is related as the approach is applicable to classes as optional, expected .... It is related because you are not proposing any Monadic interface (bind,unwrap) but just a PossibleValued interface.
That would be the traditional way of implementing this. I chose not to follow that design in this particular case. I've no problem as far as the common implementation is hidden and doesn't guide the concrete interface. I believe that generic programming must be built on top od concepts, not inside a intrusive class that know how to do everything. I think you may not have studied the source code sufficiently.
Not at all. Not yet ;-)
basic_monad definitely does not know how to do everything.
basic_monad takes a policy class to flavour it with personality. The policy class is a type full of typedefs and constants setting up what this particular basic_monad will do. The storage layout is strictly separately defined by policy than the public member functions mixed in by the policy to the final basic_monad class instantiated.
So, you could use a std::variant for the storage layout, or three std::optional<>'s, or three unique_ptr<>'s, or more excitedly, a std::expected<T, E>! Up to the definer.
The public member functions provided by this particular basic_monad are customisable by a separate sub-policy policy. basic_monad itself provides all the public member functions guaranteed to be common across all basic_monad varieties, though it can and does call into the policy class for implementation. This common interface allows a common machinery to be built on top, so specifically the implicit conversion from less to more representative, and the TRY operation.
So yes the above is intrusive, it forces an outcome-y thing to always be made. This is what I don't like. It is if in order to use rang algorithms I would need to wrap any possible range. We want to be able to use any range without wrapping them. But how you implement it is up to you, so long as you fulfil the Outcome public contract.
Niall, I'm not against not for you library. We're just reviewing it now. For me the goal is to improve it, and if we can at the same time improve the std expected proposal this will be very valuable to the C++ community. I would be very glad if this process helps the Expected proposal. Expected is a great proposal, it needs into the C++ standard.
It is weird, that you say that it is a great proposal, but you found something that must be added, modified or removed. I would like to understand these differences, because surely the expected design must be improved when I would take into account *valid* use cases that I have not considered yet. I nitpick tiny corner case problems, but I consider the overall proposal to be great. I do thank you personally in Outcome's Acknowledgement for all your service in making Expected happen, without all your work Outcome would not exist and my other C++ libraries using Outcome would be the worse for it.
You are welcome. Vicente

So yes the above is intrusive, it forces an outcome-y thing to always be made.
This is what I don't like. It is if in order to use rang algorithms I would need to wrap any possible range. We want to be able to use any range without wrapping them.
Just to be clear, are you talking a range of expected or an expected of range? Or something else? I am unsure why this policy based extensibility design would be any worse than your preferred design of linked up islands of separate implementation? After all, one can pretend Outcomes are not implemented this way, and are just islands of separate implementation also? Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Le 24/05/2017 à 17:26, Niall Douglas via Boost a écrit :
So yes the above is intrusive, it forces an outcome-y thing to always be made. This is what I don't like. It is if in order to use rang algorithms I would need to wrap any possible range. We want to be able to use any range without wrapping them. Just to be clear, are you talking a range of expected or an expected of range? Or something else? LOL.
I am unsure why this policy based extensibility design would be any worse than your preferred design of linked up islands of separate implementation? After all, one can pretend Outcomes are not implemented this way, and are just islands of separate implementation also?
See how the STL is designed. See how the Range TS is designed. Requiremen based algorithms. You are imposing a single implementation. Vicente

# Our |value_or()| member functions pass through the reference rather than returning by value (we don't understand why the LEWG proposal does except that |std::optional<T>| does, which itself is a bad design choice and it should be changed).
Well, what is the advantage to returning by reference?
You don't cause .value_or() to surprisingly fail to compile when T lacks a move or copy constructor. The reference returning edition does not impose the requirement for T to have a move or copy constructor. This choice for .value_or() interferes less on end users.
The fact optional implemented .value_or() with a return by value is a mistake. Expected need not repeat that mistake.
I might have missed something, but if .value_or() returns a reference, won't: auto &&s = std::optional<std::string>{}.value_or("temporary"s); std::cout << s << std::endl; triggers undefined behavior? '"temporary"s' is destroyed right after the declaration of 's', hence 's' is a dangling reference. By requiring .value_or() to return by value, this should just work.

The fact optional implemented .value_or() with a return by value is a mistake. Expected need not repeat that mistake.
I might have missed something, but if .value_or() returns a reference, won't:
auto &&s = std::optional<std::string>{}.value_or("temporary"s); std::cout << s << std::endl;
triggers undefined behavior?
'"temporary"s' is destroyed right after the declaration of 's', hence 's' is a dangling reference.
By requiring .value_or() to return by value, this should just work.
Does not the bind to a reference keep the std::string temporary object alive? https://godbolt.org/g/ZcLpe5 suggests that it does. But you might be onto something. Some other combination with value_or() might well produce a reference to a deleted temporary. That would explain the choice of value returning semantics for value_or(). Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

On 23 May 2017, at 03:12, Niall Douglas via Boost <boost@lists.boost.org> wrote:
The fact optional implemented .value_or() with a return by value is a mistake. Expected need not repeat that mistake.
I might have missed something, but if .value_or() returns a reference, won't:
auto &&s = std::optional<std::string>{}.value_or("temporary"s); std::cout << s << std::endl;
triggers undefined behavior?
'"temporary"s' is destroyed right after the declaration of 's', hence 's' is a dangling reference.
By requiring .value_or() to return by value, this should just work.
Does not the bind to a reference keep the std::string temporary object alive?
Yeah it does, as long as .value_or() returns by value. But if .value_or() returns by reference, the temporary is bound to reference twice. The first time is when it's bound to .value_or()'s argument, and the second time is when it's bound to 's'. Since the lifetime of temporaries will only be extended once, the second time it's bound, it's destroyed. This should demonstrate the UB: https://wandbox.org/permlink/i15zM62PF2WkTM1e
https://godbolt.org/g/ZcLpe5 suggests that it does.
Since std::optional<T>.value_or() returns by value. :)
But you might be onto something. Some other combination with value_or() might well produce a reference to a deleted temporary. That would explain the choice of value returning semantics for value_or().
Niall
-- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Niall Douglas wrote:
But you might be onto something. Some other combination with value_or() might well produce a reference to a deleted temporary. That would explain the choice of value returning semantics for value_or().
Having value_or at all is wrong. expected shouldn't have it. It encourages people to drop the error code on the floor. value_or is fine for optional, where there is no error code to erroneously ignore. An optional return tells you nothing about the reason it could not produce the value you asked for. expected does give you the reason. This reason should be acknowledged, not ignored or silently dropped. This is but one example in which "consistency" (in this case between optional and expected) makes an interface worse. These two are different things, they shouldn't be consistent. op->? Really? This is also why I don't care much for Niall's decision to include option<T> in his library, with the consequent bubbling up of option's properties (such as the ability to have an empty state) upwards into result/outcome. Sure, now that one has the empty state, one can make it do something useful, such as make error() throw when empty, so that its utility becomes a self-fulfilling prophecy. But it shouldn't be there. result/outcome would be cleaner and more elegant without option and the empty state (and result<T> could be brought as close as possible to expected<T, error_code>, an equivalence everyone expects.)

2017-05-23 0:44 GMT+02:00 Peter Dimov via Boost <boost@lists.boost.org>:
Niall Douglas wrote:
But you might be onto something. Some other combination with value_or()
might well produce a reference to a deleted temporary. That would explain the choice of value returning semantics for value_or().
Having value_or at all is wrong. expected shouldn't have it. It encourages people to drop the error code on the floor.
value_or is fine for optional, where there is no error code to erroneously ignore. An optional return tells you nothing about the reason it could not produce the value you asked for.
expected does give you the reason. This reason should be acknowledged, not ignored or silently dropped.
+1 for all this about value_or()
This is but one example in which "consistency" (in this case between optional and expected) makes an interface worse. These two are different things, they shouldn't be consistent. op->? Really?
This is also why I don't care much for Niall's decision to include option<T> in his library, with the consequent bubbling up of option's properties (such as the ability to have an empty state) upwards into result/outcome.
I agree: I find it difficult to imagine where `option<T>` would be of use. On the other hand, empty state is not something inherited from `option<T>`. It is simply that you need it to have a simple implementation of `result<T>`. What would a default-constructed `result<T>` do?
Sure, now that one has the empty state, one can make it do something useful, such as make error() throw when empty, so that its utility becomes a self-fulfilling prophecy. But it shouldn't be there. result/outcome would be cleaner and more elegant without option and the empty state (and result<T> could be brought as close as possible to expected<T, error_code>, an equivalence everyone expects.)
Agreed. But maybe it is not implementable without costs exposed even to users (like performance). Regards, &rzej;

Andrzej Krzemienski wrote:
On the other hand, empty state is not something inherited from `option<T>`. It is simply that you need it to have a simple implementation of `result<T>`. What would a default-constructed `result<T>` do?
The usual approach is to default-construct a T, although in the course of this review I came to the conclusion that there's a strong argument to be made for constructing make_error_code( uninitialized_result ) instead.

2017-05-23 16:03 GMT+02:00 Peter Dimov via Boost <boost@lists.boost.org>:
Andrzej Krzemienski wrote:
On the other hand, empty state is not something inherited from
`option<T>`. It is simply that you need it to have a simple implementation of `result<T>`. What would a default-constructed `result<T>` do?
The usual approach is to default-construct a T, although in the course of this review I came to the conclusion that there's a strong argument to be made for constructing make_error_code( uninitialized_result ) instead.
:) yes, you already have an "empty" state. Even in `std::expected`. Regards, &rzej;

Le 23/05/2017 à 15:45, Andrzej Krzemienski via Boost a écrit :
2017-05-23 0:44 GMT+02:00 Peter Dimov via Boost <boost@lists.boost.org>:
Niall Douglas wrote:
But you might be onto something. Some other combination with value_or()
might well produce a reference to a deleted temporary. That would explain the choice of value returning semantics for value_or().
Having value_or at all is wrong. expected shouldn't have it. It encourages people to drop the error code on the floor.
value_or is fine for optional, where there is no error code to erroneously ignore. An optional return tells you nothing about the reason it could not produce the value you asked for.
expected does give you the reason. This reason should be acknowledged, not ignored or silently dropped.
+1 for all this about value_or() We had expected::catch_error in the original expected proposal. I have moved this now to monad::catch_error.
This is but one example in which "consistency" (in this case between optional and expected) makes an interface worse. These two are different things, they shouldn't be consistent. op->? Really? I don't believe that opt-> is better than exp->. We don't have yet a dot operator, this is why we use ->.
This is also why I don't care much for Niall's decision to include option<T> in his library, with the consequent bubbling up of option's properties (such as the ability to have an empty state) upwards into result/outcome.
I agree: I find it difficult to imagine where `option<T>` would be of use. On the other hand, empty state is not something inherited from `option<T>`. It is simply that you need it to have a simple implementation of `result<T>`. What would a default-constructed `result<T>` do? I agree that empty doesn't add any value in those 2 cases.
Sure, now that one has the empty state, one can make it do something useful, such as make error() throw when empty, so that its utility becomes a self-fulfilling prophecy. But it shouldn't be there. result/outcome would be cleaner and more elegant without option and the empty state (and result<T> could be brought as close as possible to expected<T, error_code>, an equivalence everyone expects.)
Agreed. But maybe it is not implementable without costs exposed even to users (like performance).
This is my main concern about the generalization via policies design. Vicente

But you might be onto something. Some other combination with value_or() might well produce a reference to a deleted temporary. That would explain the choice of value returning semantics for value_or().
Having value_or at all is wrong. expected shouldn't have it. It encourages people to drop the error code on the floor.
I really don't think it does. I've certainly never felt encouraged to do that. I use .value_or() mostly in constexpr or functional use cases. I don't think I've ever used it outside those. But then I don't believe in artificially constraining users unless it's really bad for their health. I give them what they (think they) want.
expected does give you the reason. This reason should be acknowledged, not ignored or silently dropped.
Back at the beginning when designing Outcome, one of my design ideas was a variety which called std::terminate on destruction if an error had never been retrieved. So, the idea behind this family of Outcomes was that they were "single shot", .error() and .value() could be called exactly once after which the outcome became empty. On destruction, if an error had never been retrieved, it was fatal exit time. I never found a use for that particular variety in my own code, but I find the semantics very attractive. They impose rigour. I just wish I could make the statically obvious failure to collect state a compile time instead of runtime error. I could be very easily persuaded to go ahead and add that variety (recommended naming is welcomed!).
This is but one example in which "consistency" (in this case between optional and expected) makes an interface worse. These two are different things, they shouldn't be consistent. op->? Really?
This is also why I don't care much for Niall's decision to include option<T> in his library, with the consequent bubbling up of option's properties (such as the ability to have an empty state) upwards into result/outcome.
You are wrong that the option<T> => anything emptiness preservation was a motivation of mine. I was far more motivated by the power of the state of "empty" to simplify end user code. For example, the example above or the for loop examples I've mentioned before. If one were to implement the rigorous outcomes described above, you'd have implicit conversion from any less rigorous outcome to any more rigorous outcome, but require explicit conversion in the opposite direction.
Sure, now that one has the empty state, one can make it do something useful, such as make error() throw when empty, so that its utility becomes a self-fulfilling prophecy. But it shouldn't be there. result/outcome would be cleaner and more elegant without option and the empty state (and result<T> could be brought as close as possible to expected<T, error_code>, an equivalence everyone expects.)
Would you still say the above after considering my idea for a family of "rigorous" outcomes? Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

On 24/05/2017 03:26, Peter Dimov wrote:
Would you still say the above after considering my idea for a family of "rigorous" outcomes?
I must have missed that, sorry. I looked a few messages back in this thread but didn't find it.
Back at the beginning when designing Outcome, one of my design ideas was a variety which called std::terminate on destruction if an error had never been retrieved. So, the idea behind this family of Outcomes was that they were "single shot", .error() and .value() could be called exactly once after which the outcome became empty. On destruction, if an error had never been retrieved, it was fatal exit time.
I never found a use for that particular variety in my own code, but I find the semantics very attractive. They impose rigour. I just wish I could make the statically obvious failure to collect state a compile time instead of runtime error. Personally I'm not a fan of code that causes std::terminate to get called for any reason, but that comes from my biases of mostly working on code where it's ok to misbehave briefly (as it's usually self correcting, especially if you can throw an exception to abandon a
Fairly certain he was referring to the suggestion earlier in the same message, ie: On 24/05/2017 03:19, Niall Douglas wrote: particular task while letting other tasks continue) but it's not ok to stop doing everything. This is partly why I've hesitated to say anything about Outcome thus far; I'm not sure I'm part of its target audience. (I work on low-latency code as with games but either with exceptions and /EHa enabled for the soft code and not even using C++ for the hard code.)

Gavin Lambert wrote:
Fairly certain he was referring to the suggestion earlier in the same message, ie:
On 24/05/2017 03:19, Niall Douglas wrote:
Back at the beginning when designing Outcome, one of my design ideas was a variety which called std::terminate on destruction if an error had never been retrieved. So, the idea behind this family of Outcomes was that they were "single shot", .error() and .value() could be called exactly once after which the outcome became empty. On destruction, if an error had never been retrieved, it was fatal exit time.
Ah, that. Thanks Gavin. I wouldn't go as far as calling terminate(), but one could log. But for this to work, the outcome must be noncopyable (move-only). Otherwise if you make a few copies, none of it could know whether you inspected the error of some of the other copies or not. This doesn't require an empty state though, just a bit that is set (or maybe cleared) when error() is called.

Back at the beginning when designing Outcome, one of my design ideas was > a variety which called std::terminate on destruction if an error had > never been retrieved. So, the idea behind this family of Outcomes was > that they were "single shot", .error() and .value() could be called > exactly once after which the outcome became empty. On destruction, if an > error had never been retrieved, it was fatal exit time.
I wouldn't go as far as calling terminate(), but one could log.
I'd make what happens user definable of course, but the default implementation would be std::terminate. After all, these be rigorous Outcomes, failure to collect state is a logic error - it equals "your code is wrong".
But for this to work, the outcome must be noncopyable (move-only). Otherwise if you make a few copies, none of it could know whether you inspected the error of some of the other copies or not.
I had been thinking of copy constructor disabled, but there would be a clone() member function.
This doesn't require an empty state though, just a bit that is set (or maybe cleared) when error() is called.
I think the source of our disagreement is over publicly exposing the empty state. You appear to be happy if it's an internal only thing, but not available to end users. I take the view that if you're implementing the empty state anyway, might as well open it up for public use. Can you give a reason why public availability of an empty state with well defined semantics and behaviours is a bad idea? Does it naturally lead to incorrect code or something? Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Niall Douglas wrote:
I think the source of our disagreement is over publicly exposing the empty state. You appear to be happy if it's an internal only thing, but not available to end users. I take the view that if you're implementing the empty state anyway, might as well open it up for public use.
No, I don't argue for a possible, but unexposed, empty state. I argue that there should be no empty state. A result should either hold a value or an error (the reason for the lack of value).

Le 24/05/2017 à 17:31, Niall Douglas via Boost a écrit :
Back at the beginning when designing Outcome, one of my design ideas was > a variety which called std::terminate on destruction if an error had > never been retrieved. So, the idea behind this family of Outcomes was > that they were "single shot", .error() and .value() could be called > exactly once after which the outcome became empty. On destruction, if an > error had never been retrieved, it was fatal exit time. I wouldn't go as far as calling terminate(), but one could log. I'd make what happens user definable of course, but the default implementation would be std::terminate. After all, these be rigorous Outcomes, failure to collect state is a logic error - it equals "your code is wrong". I don't think we should manage this with complex implementation. We should use attributes and static analysis.
But for this to work, the outcome must be noncopyable (move-only). Otherwise if you make a few copies, none of it could know whether you inspected the error of some of the other copies or not. I had been thinking of copy constructor disabled, but there would be a clone() member function. You see. Managing this kind of possible issues make the implementation more complex than necessary. E.g. Would you remove new because the user could not store it in a variable and delete it later on?
This doesn't require an empty state though, just a bit that is set (or maybe cleared) when error() is called. I think the source of our disagreement is over publicly exposing the empty state. You appear to be happy if it's an internal only thing, but not available to end users. I take the view that if you're implementing the empty state anyway, might as well open it up for public use.
Can you give a reason why public availability of an empty state with well defined semantics and behaviours is a bad idea? Does it naturally lead to incorrect code or something?
Because it makes the user code more complex. This is the task of static analizers. We have a lot of examples maintaining publicly this empty state because the cost of ensuring this empty (partial state) is not expensive. We have it with unique_ptr, exception_ptr. However, how many lines of code have you seen that checks for whether exception_ptr contains an exception or not? Anyway, I have not understood yet when we need this empty state on the implementation and les yet when does the user needs it. I'll persevere. Vicente

On 24/05/2017 03:19, Niall Douglas wrote:
I never found a use for that particular variety in my own code, but I find the semantics very attractive. They impose rigour. I just wish I could make the statically obvious failure to collect state a compile time instead of runtime error.
I mentioned earlier that if the entire result<T> or outcome<T> class were declared [[nodiscard]] then this should have the effect of the compiler issuing warnings and/or errors if one is returned from a method and the caller fails to do *something* with it. (This came up in the context of you wondering whether some individual methods such as value() should be [[nodiscard]], but I don't think that's the correct approach.) In particular this should handily catch the case where a void-return method is changed to an outcome-return method in a later version of a library (or app-internal method), which is probably the second-most-common case of ignoring error returns (the most common of course being lazy programmers). Sadly this is C++17-only. Though I dimly recall another technique involving rvalue methods that only requires C++11 being used in another proposed library. It's possible that I am recalling incorrectly and this was also runtime-only validation, however.

On 24/05/2017 02:17, Gavin Lambert via Boost wrote:
On 24/05/2017 03:19, Niall Douglas wrote:
I never found a use for that particular variety in my own code, but I find the semantics very attractive. They impose rigour. I just wish I could make the statically obvious failure to collect state a compile time instead of runtime error.
I mentioned earlier that if the entire result<T> or outcome<T> class were declared [[nodiscard]] then this should have the effect of the compiler issuing warnings and/or errors if one is returned from a method and the caller fails to do *something* with it.
That is already the case.
Sadly this is C++17-only. Though I dimly recall another technique involving rvalue methods that only requires C++11 being used in another proposed library. It's possible that I am recalling incorrectly and this was also runtime-only validation, however.
If the compiler advertises itself as providing [[nodiscard]], Outcome uses it. Many compilers do, even in less than C++-17 mode. For older compilers, Outcome will use compiler-specific markup to achieve the same warning where available e.g. under the MSVC static analyser. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/
participants (12)
-
Andrzej Krzemienski
-
d25fe0be@outlook.com
-
Emil Dotchevski
-
Gavin Lambert
-
Glen Fernandes
-
Hartmut Kaiser
-
Ion Gaztañaga
-
Niall Douglas
-
Paul A. Bristow
-
Peter Dimov
-
Vicente J. Botet Escriba
-
Vinnie Falco