The default declaration for outcome is:
``` template < class R, class S = std::error_code, class P = std::exception_ptr, class NoValuePolicy = policy::default_outcome_policy
> requires (std::is_void<EC>::value || std::is_default_constructible<EC>::value) && (std::is_void<P>::value || std::is_default_constructible<P>::value) class [[nodiscard]] outcome; ``` Yay the Concepts TS. Anyway, therefore `outcome
` equals `outcome `. Does it make sense now?
No. Why would I pass void for S or P?
You wouldn't, as the voided types are not particularly useful.
However as a fundamental vocabulary type highly useful in constexpr
metaprogramming, being able to be configured with void is flexible. It
can also be useful in internal macro boilerplate. And finally, Expected
permits expected
By the way, such policy-based design is probably not a ideal for error-handling library return types, for the same reason shared_ptr<T> is better than Alexandrescu's policy-based smart pointer, and for the same reason function<T> (as it is now standardized) is better than function
(as it was in Boost initially, before I fixed it): it leads to much increased coupling, and ABI incompatibilities. The thing is, error objects, like smart pointers and function objects, need to be as frictionless as possible when crossing API boundaries, and policy-based designs work directly against that goal.
Completely agreed for a std::result
> Take this for what it's worth, but the earlier outcome design was a lot > more focused.
You may be forgetting my initial claims of a "multi-modal" design. v1 wore many hats. v2 has no head, so it cannot wear a hat.
I'm afraid my reading is that you're shifting important design decisions to the user, who has (by definition) lower qualifications in the domain of error handling. Flexibility is not always good, after all we all have a C++ compiler and can handle errors with all the flexibility we need -- without an error handling library.
v1 Outcome's core type was `template<class Policy> class basic_monad`. Everything user facing was typedefed to that with a default choice of policy. v2 similarly defaults the choice of policy. v2 is neither more nor less flexible. End users get the same degree of customisation power. Whether they use it wisely or not is up to them, as a vocabulary type it's not my remit to enforce good behaviour by library developers, just to encourage it. If they want to shoot themselves in the foot, they can.
v2 now concepts matches instead. If you feed it a type matching an error_code concept, it treats it as an error code, otherwise it does not. Same for exception_ptr. Thus `outcome
` is legal, but unusable, because you cannot construct one without resorting to UB. Assuming we agree that passing int for the P makes no sense at all:
Not at all. There is nothing wrong with int as either an exception or a payload type. Some end user may want that for some reason. For the exact same reason, you can throw an int in C++. It can make sense.
1. Could you show a practical case that illustrates the need for using anything but std::exception_ptr for P?
The idea behind P is that when trait::is_exception_ptr<P> is false, then
P is a payload type which provides additional information for your main
EC failure type. So:
outcome
2. What is the benefit of P being semantically different from S? In other words, what is the difference/benefit of outcome
over expected ?
Peter Dimov wanted the ability for the Filesystem TS to return the
exception which would have been thrown in the error_code& overloads, so
calling code can effectively filter whether to throw the exception or
not. I can see that as a pretty useful use case, albeit expensive seeing
as an exception_ptr is expensive to allocate. But still a reasonable
half way house between a full throw-catch cycle and the currently
metadata impoverished error_code& overloads.
So outcome
> The other significant difference you introduced post-review was that > outcome no longer had strict value-or-error semantics. This too helped set > it apart.
In the default configuration, outcome
is only strictly value-or-error, value-or-exception, or value-or-error+exception. What other options are out there? What could I do with a struct with 3 members, which outcome "strictly" forbids?
Things you can't do: * value + error * value + exception * value + error + exception
> But now, and it may be just me, but I am confused. What exactly is the > difference between outcome and the two flavors of expected we have? Is it > essentially the same as expected
, except that in outcome the strict > value-or-error semantics (that you may remove later) are optional? It's a low level subset. Struct-based storage, not variant-based. Fast. Lightweight. ABI stable. But not rich, it's a barebones type.
Are you claiming that outcome
is faster than expected ? I don't see how could that be true. Perhaps I'm missing something.
It should be to compile. Outcome is essentially nothing :)
I'm also puzzled by the ABI stability claim. I mean, policy-based designs are only ABI-stable if most users use the same policies, which obviously defies the point of using policies.
Remember Outcome permutes its namespace with the git SHA. So it's effectively randomised, and thus no symbol collisions can occur. The only ABI instability is thus the data layout, and as I've mentioned I've deliberately kept result's data layout C-struct compatible.
> This might be reasonable if it is a one-off thing, but consider that the > general case is a bit more complicated, possibly involving different > compilation units, and you do need to ensure that when the thread > terminates "state" doesn't contain an error. It may or may not make sense > to add something to that effect to Outcome.
v2 is designed to be subclassed into localised implementations in a local namespace, which is a new thing. So it's very easy to add additional behaviours and features to your localised implementation, whilst retaining the cross-ABI interoperability between many localised implementations.
v2 only provides the raw building block. What you do with it after it totally up to you.
The question is what's the value in using Outcome as a building block? Why would anyone bother, if using Outcome they're basically free to do whatever?
Using your logic, why use std::pair or std::tuple when a struct will do? Same argument.
For example, one could build your Noexcept library with it quite easily, and thus "plug in" to Expected, P0650 etc.
I think Noexcept is a lot more lightweight than you think. The internal machinery it is based on (what I think you imagine your building block would replace) is about 500 lines, out of about 800.
As you saw on SG14, using any black box library routines is always going to be a problem for the fixed latency user base. Your library is shorter than mine, but it calls unknown latency routines. Mine never does. You also impose TLS on your end users. Mine is much lower level than that, if end users wish to combine mine with TLS to achieve your library, they'll find it very easy. So why innovate for them? Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/