
std::error code addresses your concern about potential translations of exceptions. It does not address your concern about communicating arbitrary data. But in the end it might turn out to be a reasonable trade-off for low-level libraries that disable exceptions.
I entirely agree. There is a tradeoff here, a balance. I've always been of the opinion that if you want to return arbitrary data, either you encode it into the type system and thus increase coupling, or you type erase it usually using malloc. Exception throws are a very special case of this because the C++ runtime does the type erasure for you. Emil you have pointed out many times that the C++ compiler ought to optimise a try { throw x; } catch(x) { ...} by eliding the throw-try-catch in runtime. And I agree, but none of the major compilers do do that, and for a long list of good legacy reasons. Thus in terms of compiler technology available here and now and today - not a maybe tomorrow - there are plenty of use cases where taking on more inconvenience and ugliness in some parts of your code can be beneficial.
However it looks like Outcome provides a solution for most of the practical cases, while leaving the general case unsolved. Boost.Outcome is a set of tools (rather than just one) and you are expected to choose one that best solves your particular problem.
I was just about to say the same thing, but more pointed. Exception specifications were a bad idea because of the potential presence of unknown code (despite what Peter says, I do and did not find exception specifications to be useless in theory). That makes them a bad idea in *general* i.e. as a rule of thumb. But that says nothing about the local use case where no unknown code may operate, and all code potentially executable is known both to the programmer and the compiler. In that situation, I do believe that in a localised use case, exception specifications can add significant value. Knowing that a piece of code will never, ever see stack unwinding lets you skip handling stack unwinding. Hence `noexcept`. Furthermore, unlike with exception specifications which were unhelpfully checked at runtime, Outcome fails at compile time. That's a very different kettle of fish. You can't successfully compile code if your E types don't have interop specified for them. Again, Herb's article is right that statically checked exception specifications are a bad idea in *general*. But they can be very useful *locally*. Indeed, that's the whole point of the Expected proposal, and WG21 has greenlit that one. It's coming to future C++ whether you like it or not (bar some major surprise).
Remember that the goals you list and arguments that Herb Sutter draws apply to a general failure object transportation mechanism present everywhere in the program, in any program. In contrast, Outcome is not intended to be a failure reporting mechanism in the entire program: it is either to be used in isolated places (with particular conditions), or in programs with extremely harsh execution constraints, where many inconveniences are expected, including the inability to freely transport arbitrary amount of failure information in arbitrary form.
Absolutely agree. I always envisaged Outcome being useful only within a low level layer of code close to the bare metal. Code where a unknown potential ten thousand CPU cycles might matter. Anybody who doesn't care about that kind of stuff doesn't have much need for Outcome.
So, case 1. You are using in your program a boost::filesystem2 library ("2" because we hypothetically assume it uses Boost.Outcome to report failures). Your program can freely use exceptions. But often the inability to write to the file is not something you have to propagate up, but you know how to handle it locally. This is not a generic context. I exactly know what library I am using, and one level up there will be no `result<>`, there will only be exceptions. In this case the most proper tool from Outcome toolbox is to create your own type representing the failure code and two file names, and the usage of `result<>` with your type: `result
`. The question "how this interacts with `result `" is irrelevant, because there will never be such interaction.
Completely agree. And much of the additional "unnecessary" complexity in Outcome is to handle what happens when a programmer is faced with two third party libraries using Outcome/Expected with incommensurate types. Implementing a non-source-code-intrusive mechanism to let the programmer inject what to do comes with messy complexity, some of which Rob pointed out in his review. I don't expect that situation to emerge at all in most code for at least five years. But as a userbase grows, especially after Expected enters C++, then that situation will emerge frequently. And at that point Outcome has them covered (and unlike Expected or anything proposed currently to WG21 I might add).
There are probably more cases, but my point is:
1. Outcome is not meant to be a full failure-handling framework for every part of every program (even though it is technically possible to use it this way).
Absolutely agreed. I felt it was important that it is technically possible to completely replace exceptions with Outcome throughout a large codebase. I would recommend anybody to not do that though.
2. It addresses the specific cases through specific trade-offs where it does not have to address all the issues of the full failure handling framework.
Also absolutely agreed. Niall -- ned Productions Limited Consulting http://www.nedproductions.biz/ http://ie.linkedin.com/in/nialldouglas/