Boost logo

Boost :

Subject: Re: [boost] [review] Review of Outcome v2 (Fri-19-Jan to Sun-28-Jan, 2018)
From: Niall Douglas (s_sourceforge_at_[hidden])
Date: 2018-02-03 00:20:37


> So, my vote is based on (in no particular order):
>
> 1) In these discussions, I can clearly recognize the dislike for
> exception handling and even C++ (I don't mean by you personally) that I
> have been exposed to in the past, since for years I've been surrounded
> by people who falsely believed that they can't afford exceptions or
> smart pointers or proper serialization, and they have strong, if
> incorrect, opinions on what's wrong with C++. I believe that this
> attitude does not belong to Boost. It's possible that I got this wrong.
> It may be interesting to know how many of the current users of
> "standalone" Outcome use Boost in "low latency" environments or at all.
> Do you have an idea?

No, nor do I think it matters. The rationale to use Outcome for me in my
code is to let the caller decide whether an exception should be thrown
or not, rather than the function experiencing the failure hard coding an
exception throw which invokes an unavoidable table search I'd like to
avoid. That's my primary use case in my own code.

My secondary use case is that rather than encode the logic for how to
type erase/rethrow/convert the failure into an exception throw via a
preprocessor macro which is how it's usually done e.g.
BOOST_THROW_EXCEPTION(), I'd like to set rules via the type system for
what is to happen. No macros needed. The policies are very useful for
this use case.

My tertiary use case is accumulation of unknown failures into my own
test framework. Failures can come from anywhere, including the STL, and
I preserve all the original information. No information loss at any
point, and a complete chain of execution is recorded by the test
framework which can be checked for correct handling of failure. The 16
bits of spare storage in Outcome combined with the ADL construction
hooks and TLS is very useful for this use case.

Now, lots of people have other use cases for Outcome, but those three
are my main personal motivating use cases. And Outcome is a superb
solution for my needs, else I'd not have invested so much effort into it.

> 2) Clearly, Outcome _does_ want to help pass errors across API
> boundaries, including in generic contexts. The problem is that
>
> result<T,E> compute() noexcept;
>
> is very similar to
>
> T compute() throw(E);
>
> (yes, I know exception specifications are enforced dynamically, but
> that's not what's wrong with them, see the second question here:
> https://herbsutter.com/2007/01/24/questions-about-exception-specifications/.)
>
> My reasoning is that if with Outcome you can always return the exact
> error type you've specified in your static interface, the same approach
> would work for (perhaps statically-enforced) exception specifications.

The exception specification analogy doesn't apply to Outcome. What
doomed exception specifications is indirect function calls combined with
the side channel exception throws operate through, so your function
which guarantees to never throw anything but E happens to call some
overriden virtual function which throws a different type, and boom
you've just called std::terminate.

That's because exception throws operate via a side channel outside the
normal flow of execution. Outcome doesn't have that - it returns via the
normal flow of execution. Therefore we can hard guarantee that if the
program compiles, the "exception specification" is met. Overriding a
virtual function will only compile if the return type matches
result<T,E>. So overrides can't introduce hidden calls to
std::terminate. Therefore exception specifications are the wrong analogy
for result<T, E> returning code.

> Logically, to address this concern you could:
>
> - Demonstrate that there is a major flaw in my analogy, or
>
> - demonstrate that exception specifications could be made practical,
> including in generic contexts, possibly by using some clever
> policy-based design, or
>
> - provide an interface that can forward arbitrary errors ot the caller.
>
> (I see these as mutually-exclusive).

As I pointed out to you during Outcome v1 review, you can implement TLS
push and pop for Outcome just the same as your Noexcept library does.

As an example, in AFIO, all errored results are recorded into a TLS
ringbuffer which tracks the execution log, so for any given error, AFIO
can tell you the exact sequence of API calls, including to internal
functions, and their parameters, leading up to that point. It also can
tell you exactly how AFIO handles the failure, every single function
called, including internal ones, as the stack is unwound. I haven't
implemented it yet, but it'll all designed to get fired into a file so
it acts as an i/o validation audit log, but right now its main use is
for validation testing, so the test suite can say if the correct
execution paths were taken for some given failure scenario.

Outcome doesn't come with such features builtin. But it provides a rich
set of customisation points to let you roll any bespoke error and
exception handling framework you like. It really is very handy to have
in the toolbox, abstracting out the handling of failure into a generic
framework is something we've not done much work on in C++ in recent years.

Niall

-- 
ned Productions Limited Consulting
http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk