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