
2017-05-22 22:52 GMT+02:00 Niall Douglas via Boost <boost@lists.boost.org>:
This design is intentional. *If* you learn off the "rescue semantics" as you put them, then when writing code you swap tedious boilerplate for code which relies on those rescue semantics.
Yes, I think I understand what you are saying. If `o.error()` has a "narrow contract" or IOW, if it has a precondition, many users (but not all) would be forced to manually repeat the same unpacking code:
``` if (o.has_value()) use (error_code_extended{}); else if (o.has_exception()) use error_type((int) monad_errc::exception_present, monad_category()) if ...
```
Correct. The default actions aim to do a useful action on use, thus allowing the programmer to skip manual checks. They can of course still manually check if they wish to override the default action.
If you feel that a woolly and imprecise step too far, then you can use expected<T> instead. It's why Outcome ships with expected<T>. Each of its observers come with strict preconditions governing whether the call is valid or undefined behaviour.
I probably could, but it sounds like `outcome<>` didn't have anything else to offer except the "wooly semantics", and I do not think it is the case.
I would say that is exactly the case.
It is my understanding that `outcome<>` also offers other things:
- being able to store either an error code or exception_ptr
Yes outcome<T> can store all four states: empty, T, EC or E.
- other convenience interface without "contract widening", like BOOST_OUTCOME_TRY
BOOST_OUTCOME_TRY works perfectly with expected<T, E> too. And all basic_monad flavours.
- other performance improvements
There are no performance improvements. These are the actual implementations of the public "classes" provided by Outcome:
template<class T, class E = std::error_code> using expected = basic_monad<policies::expected_policy<T, E>>;
template<class T> using outcome = basic_monad<policies::monad_policy<T, error_code_extended, std::exception_ptr>>;
template<class T> using result = basic_monad<policies::monad_policy<T, error_code_extended, void>>;
template<class T> using option = basic_monad<policies::monad_policy<T, void, void>>;
In other words, all four are the exact same implementation, identical in every way. They just have different "personality".
This is why it's result<T> and not result<T, E = error_code_extended>. The design premise is that someone wanting a result<T> not using error_code_extended simply template aliased their own custom result<T> type.
I originally expected that AFIO v2 would create its own custom but extended result<T> called io_result<T> reusing the above framework. But it turned out to be overkill for what AFIO needed, so AFIO v2 actually typedefs result<T> directly into io_result<T> and it works well.
If I want to use the above advantages, but I dislike "contract widening" (or "wooly semantics"), you leave me with no option.
You can use expected<T, E> if you want narrow contracts.
Or you can roll your own custom implementations! The policy class infrastructure is very straightforward and very easily adapted into any mix you like.
Storage is also policy driven, so if you feel like using a std::variant or malloced memory or mutex locked state, that's fine too.
What you could do is to offer two observer functions:
``` o.error_wide(); // with wide contract o.error_narrow(); // with wide contract ```
Don't look at the choice of names, you can make them better, but the idea is you have two functions: one for people who prefer clearly stating intentions at the expense of longer code, the other for people that prefer concise notation.
This is what `std::optional` and `boost::optional` are doing, you get both:
``` *o; // with narrow contract o.value(); // with wide contract ```
Or:
``` if (o == true) // for those who like short notation if (o && *o == true) // for those who like no ambiguity ``` My proposed names:
- as_error() // for wide contract - error() // for narrow contract
It would fit much better with the design of Outcome if these were new typedefs of basic_monad.
How about these for the narrow contract editions of outcome<T>, result<T> and option<T>:
- outcome_u<T> - result_u<T> - option_u<T>
Ok, so are you saying that `basic_monad` (by now probably something like `outcome_base`) is part of this library's public API? But the documentation leaves me with little information as to how I can use it. Unless I missed it, I recommend that you provide a guide in the docs how one can compose one's own type, and a mention that it would blend nicely with other outcome-like objects, e.g., that BOOST_OUTCOME_TRY will still work for a custom outcome. Also, the reference seems to be missing some information. If I go to `basic_monad`: https://ned14.github.io/boost.outcome/classboost_1_1outcome_1_1v1__xxx_1_1ba... The first thing that interests me: this is a template parametrized by `implementation_policy`. What constraints does a type `implementation_policy` need to satisfy to be a valid policy and meet this library's requirements? For sure, it cannot be just any type, like `int`. What I am missing here is the concept (not in the sense of concepts Lite, but the description of requirements as in here: http://www.sgi.com/tech/stl/ForwardIterator.html). Regards, &rzej;