
2018-01-28 5:02 GMT+01:00 Emil Dotchevski via Boost
On Sat, Jan 27, 2018 at 5:33 AM, VinÃcius dos Santos Oliveira < vini.ipsmaker@gmail.com> wrote:
2018-01-27 0:31 GMT-03:00 Emil Dotchevski via Boost < boost@lists.boost.org
:
Question: if using the OUTCOME_TRY macro is equivalent to calling the function, checking for error and then returning an error if there is an error, how is this different from using exceptions? Semantically, exception handling does nothing more than check for errors and returning errors if there were errors
There is a single control flow to analyse: the return of the function. You don't need a "parallel" control flow construct to check for error case.
Where is the parallel control flow in return parse(read_data(open_file()))?
- You can't forget to check the error case (it's part of the type system).
You can't forget to check for errors if you use exceptions, either.
Literally, if you use exceptions it is as if the compiler writes the ifs for you.
- It's self-documenting.
Only to the extent that you can see that a function may return an error.
With exceptions, except for noexcept functions, functions may "return" an error.
Some would count the fact that with e.g. Outcome you can specify what kind of errors can be returned as an advantage, but that is similar to statically enforced exception specifications. Sutter explained why that is a bad idea back in 2007: https://herbsutter.com/2007/ 01/24/questions-about- exception-specifications/.
- There are no strange interactions between Outcome and the rest of the language (e.g. throwing destructors, transporting exception
between
threads, and so on...).
So, don't throw in destructors. Also, you can't use Outcome in destructors, but that is fine -- it is a logic error to not be able to destroy an object.
Though this reminds me: in C++, exceptions are the only way constructors may report an error, and this is very deliberate, integral part of RAII. This guarantees that you can't use an object that failed to initialize, which is the reason why member functions are free to assume, rather than check, that all invariants of the class have been established.
Thus, exception handling is an integral part of the C++ object encapsulation model. Choose to not use exceptions and the result is that like in C, each function must check whether the object was initialized, and return some error code to indicate that condition. You're replacing a bullet-proof automatically enforced error checking system with a manual one, prone to errors; worse, we're talking about error handling code, which by its very nature is difficult to test.
There is a usage model that allows you to both report failures via result and have your types retain strong invariants. Once you have decided to use "explicit failure execution paths" approach in a library, you provide factory functions rather than constructors as the interface for creating new objects. This solves another long-standing problem of C++ constructors: that they do not have names and sometimes you do not know what they do: vector<char> v1 (20); // size or capacity? vector<char> v2 {size_t(20), 'c'}; // 20 elements or just 2? vector<T> v3 {x.begin(), x.end()} // 2 elements or x.size() elements? point<double> p {1.141, 0.535}; // polar or Cartesian co-ords? Regards, &rzej;