On Mon, Jun 12, 2017 at 1:50 PM, Andrzej Krzemienski via Boost < boost@lists.boost.org> wrote:
2017-06-12 22:28 GMT+02:00 Emil Dotchevski via Boost < boost@lists.boost.org> :
On Mon, Jun 12, 2017 at 1:15 PM, Andrzej Krzemienski via Boost < boost@lists.boost.org> wrote:
2017-06-12 20:07 GMT+02:00 Emil Dotchevski via Boost < boost@lists.boost.org> :
On Mon, Jun 12, 2017 at 2:42 AM, Niall Douglas via Boost < boost@lists.boost.org> wrote:
On 12/06/2017 09:22, Emil Dotchevski via Boost wrote:
The lively debates during the Outcome review show that there is a great deal of interest in solving the problem of error handling in environments where C++ exception handling is unavailable.
Noexcept is a new C++11 library that implements a different approach to solving the same problem. Any feedback is welcome.
The use of functional throw(), try() and catch() was a design approach rejected very early by me and most who have looked into this problem.
Nobody wants to reimplement via a library exception handling with exceptions disabled. It's an impoverished experience, and leads to brittle code.
Can you elaborate? My understanding is that the problem with exception handling is the unpredictability of the performance you'll get. Noexcept directly addresses that issue by not introducing the unpredictability of its own return type which may or may not get optimized.
It also removes the redundancy of requiring types which already have a useful empty state to be wrapped into something like outcome<>. Nobody would return optional
from a function that may fail, they'll just return FILE */nullptr. Returning outcome is similarly redundant and possibly inefficient. Just enable C++ exceptions if you want exceptions.
I agree, the question is what to do if you can't.
There is a number of expectations people have or might have form error-handling framework:
1. Predictable times. 2. When I forget to check for error, the computation should not silently proceed. 3. Not polluting the function return type 4. Explicit control flows. 5. No explicit control flows. 6. Neutrality for some functions, elspecially those extern "C". 7. Being fast. 8. Being able to carry any payload.
Obviously, a framework cannot guarantee all of these, and trade-offs need to be made. One thing that both exceptions, and outcome<>/expected<> have is #2: when you forget that the function might fail, and it fails, the dependent functions will not get called.
What do you mean "dependent functions"?
In case of exceptions this is owing to stack unwinding. In case of outcome, it is because the program will not compile.
Can you post an actual example so we're not talking in the abstract?
``` int job(int x) { int y = f(x); // f might fail, but I forgot int z = g(y); // g might fail, but I forgot return h(z); } ```
If for some reason, I have forgotten that f() might fail (and signal failure), does function g() get called? In case of exceptions no, because if f() throws, then g() is never called. In case of outcome<>: no, because f() returns `outcome<int>, so the above will fail to compile, an I will be forced to rewrite function job().
If you choose to write this code, in Noexcept you will get an assert. Even in NDEBUG builds, let's not forget that if f() fails, y is invalid and g() should reject it (think of it as e.g. fread getting a 0 for its FILE * parameter). Perhaps your point is that ideally f() shouldn't return int but a different type with special semantics. Okay. OTOH let's say you're returning a file descriptor. I'd think that optional<int> is an overkill in this case, in fact you're probably obfuscating the fact that the int is a FD -- what does it mean if you get -1 FD in an optional<int> (or for that matter in an outcome<int>)? It's redundant, and after all it's a safe assumption that you won't get silent failures from functions if you pass -1 for the FD. That said, Noexcept works great with optional, too: int job(int x) { optional<int> y = f(x); // f might fail, but I forgot int z = g(y); //if g takes int, you'll get a compile error, just like with Outcome return h(z); } (if you did y.result() it'd throw in case of failures (optional<> or not), so if you call .result() you can't accidentally pass the bad value to g.)