
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/