Boost logo

Boost :

Subject: Re: [boost] Error handling libraries and proposals
From: Niall Douglas (s_sourceforge_at_[hidden])
Date: 2017-07-11 00:58:39

>> Are you confusing result and outcome?
>> result<T, EC>
>> outcome<T, EC, E|P>
>> The default template parameters give exactly the same as with v1. But if
>> say you chose `result<int, long>` or `outcome<int, long, std::string>`
>> then that works too.
> I still don't understand what does it mean to use void. I don't understand
> what this means:
> "outcome<void, void> would equal outcome<void, void, std::exception_ptr>"

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<R, S, P>
  (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<void, void>` equals
`outcome<void, void, std::exception_ptr>`.

Does it make sense now?

(I think you'll find the synposis at clears everything up
for you)

> 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.

> Reading the documentation, some of it sounded like
> evangelization of std::error_code, so the conclusion I drew was that the
> idea is that you don't specify the error types because you should only be
> using std::error_code, except that in the real world you might get an
> exception from somewhere, in which case outcome lets you stuff a
> std::exception_ptr into it too. That makes sense, even if I think that it
> is not practical to assume that everyone will jump on the std::error_code
> train.

That was purely a simplifying narrative which was taken due to
continuing Reddit confusion. v1 always could do far more that the
tutorial suggested. You could, in fact, customise any of the types to
anything you liked so long as you met an error_code or an exception_ptr

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<int, int, int>` is legal, but
unusable, because you cannot construct one without resorting to UB.

> On the other hand, expected<T,E> (Vicente) says no, std::error_code is not
> the only option so this should be a template parameter. On top of that,
> expected<T,E...> (Peter) says well, at the very least you might get an
> exception from somewhere so you have to be able to have e.g.
> expected<T,std::error_code,std::exception_ptr>, so we'll take more than a
> single E.

v2 was designed to dovetail into Expected neatly. It's basically a
hugely simplified and thus much faster to compile subset. That should
allow Expected to take on much more monadic stuff, if Vicente prefers.

> 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<T, EC, E|P> is only strictly
value-or-error, value-or-exception, or value-or-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<T,E...>, 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.

> 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

v2 only provides the raw building block. What you do with it after it
totally up to you. For example, one could build your Noexcept library
with it quite easily, and thus "plug in" to Expected, P0650 etc.

> By the way, this illustrates that errors and successful return values are
> very different semantically, which is why at least sometimes it is
> necessary to find a different channel to pass errors out. At which point
> one must ask what is the downside of using TLS for the error object? What
> is the design rationale for insisting on stuffing errors into return
> values, moving them one level up at a time, when in reality only the
> reporting and the handling code care about the error?
> The benefits of always passing errors through TLS are 1) it effectively
> decouples successful results from error conditions, which in turn means
> that only the error-reporting and the error-handling code are coupled with
> the error object or its type; and 2) it doesn't require the enumeration of
> all possible error types that a function may "return" (I'll once again
> refer the reader to exception specifications as to why that is a bad idea).
> The reason why not requiring the enumeration is linked to TLS use is that
> it requires the storage to be "large enough", which is perhaps too large to
> stuff into a return value (since we can't assume that a dynamic allocation
> is permissible.)

v2 Outcome is way lower level than any of that. It's a raw vocabulary
type, pure and simple, nothing provided other than absolute bare
minimum. Ready to act as a foundation stone for bigger stuff.


ned Productions Limited Consulting

Boost list run by bdawes at, gregod at, cpdaniel at, john at