2017-06-20 3:38 GMT+02:00 Emil Dotchevski via Boost
On Mon, Jun 19, 2017 at 2:41 PM, Andrzej Krzemienski via Boost < boost@lists.boost.org> wrote:
2017-06-19 20:33 GMT+02:00 Emil Dotchevski via Boost < boost@lists.boost.org> :
On Mon, Jun 19, 2017 at 2:58 AM, Andrzej Krzemienski via Boost < boost@lists.boost.org> wrote:
2017-06-14 21:52 GMT+02:00 Richard Hodges via Boost < boost@lists.boost.org
:
Exception return paths are not infinite. There are a finite number of places in code that an exception can be thrown.
The exception path is one path, the non-exception path is another. That’s two in total. Exactly equivalent to an outcome<>.
It is a fallacy to say that there are an indeterminate number of paths.
If developers do not understand RAII, then an afternoon of training can solve that.
RAII is the foundation of correct c++. It is the fundamental guarantee of deterministic object state. A program without RAII is not worthy of consideration. The author may as well have used C.
Perhaps there is an argument that says that RAII adds overhead to a program’s footprint. If things are that tight, fair enough.
Otherwise there is no excuse to avoid exceptions. I’ve never seen a convincing argument.
The above statement almost treats RAII and exception handling as synonymous. But I believe this gives the false picture of the situation.
RAII is very useful, also if you do not use exceptions, but have multiple return paths. You want to acquire the resource in one place and schedule its future release in one place, not upon every return statement.
If you adopt this programming style as a rule, there is no downside to using exceptions.
In case of using things like Outcome, you still want to follow RAII idioms.
People who choose to use Outcome do understand RAII and will still use it. But RAII does not handle all aspects of failure-safety, and this is about these other aspects that people may choose to go with Outcome rather than exceptions. One example: propagating information about failures across threads, or "tasks".
There is exception_ptr for transporting exceptions between threads, which was the only primitive that was missing for being able to accumulate results from multiple workers.
Outcome and Noexcept are simply better alternatives for users who write or maintain code that is not exception-safe -- better not compared to exception handling (which in this case can not be used), but compared to what they would likely do otherwise.
To somewhat challenge this statement, The following is an example of how I would use Boost.Outcome if I had it available at the time when I was solving this parsing problem: https://github.com/akrzemi1/__sandbox__/blob/master/outcome_ practical_example.md This tries to parse (or match) the string input, where I expect certain syntax, into a data structure. It is not performance-critical, I do not mind using exceptions, but I still prefer to handle situations where the input string does not conform to the expected syntax via explicit contrl paths. A number of reasons for that:
1. I want to separate resource acquisition errors (exceptions are still thrown upon memory exhaustion) from input validation.
Why?
I think the reason is that of a personal taste or personal sense of order. Failure to acquire resources is a situation where I will not be able to deliver what I have committed to. I will disappoint the user. In case of validation failure, this is exactly what users are calling my function for: sometimes their only goal is to get a true-false answer whether their text file is valid. I do not even treat validation failure as "error". But I still like to have the "short exit" behavior of errors.
2. Some debuggers/IDEs by default engage when any exception is thrown. I do not want this to happen when an incorrect input from the user is obtained.
"By default", so turn off that option.
But after a while I have concluded that it is a good default. Even if I am debugging something else, if I get a "resource-failure", or "logic error" (like invariant broken) I want to be alerted, and possibly stop what I was debugging before. This default setting is my friend, provided I do not use exceptions for just any "irregularity".
3. I want validation failers to be handled immediately: one level up the stack. I do not expect or intend to ever propagate them further.
You can catch exceptions one level up if you want to. Right? :)
I can. And it would work. But it just feels not the right tool for the job. It would not reflect my intention as clearly as `outcome`.
However, if you're only propagating errors one level up, it really doesn't matter how you're handling them. I mean, how much trouble can you get into in this case? It's trivial.
But t reflects my intentions clearly and gives me confidence that the error information will not escape the scope if I forget to put a try-block, or if I inadvertently add another return path form my validation routine.
Error handling libraries are needed in more complex use cases where errors must be propagated across multiple levels, across threads, across API boundaries. The important design goals are:
1) The error object created by reporting code should be able to be propagated across (potentially many) error-neutral contexts which should not be required to "translate" it (that is, turn it into a different error object.) The idea of translation of errors gave us exception specifications which are notoriously one of the more embarrassing aspects of C++.
2) Error-neutral contexts should be able to ignore any errors reported by lower level code but also intercept _any_ error, augment it with relevant information (which may not be available at the point the error is detected) and let it propagate up the call stack, intact.
3) Error-handling contexts should be able to recognize the errors they can deal with but remain neutral to others.
I recognize these needs. And in the contexts where you require the above characteristics (probably 97% of all code) exceptions are the tool for the job. For rare situations where I need different characteristics of error reporting mechanism, I will need to resort to something else, like a dedicated library.
Your use of outcome is probably fine in this simple case but
out::expected
parse_range (const std::string& input) looks much too close to exception specifications:
Range parse_range(const std::string& input) throw(BadInput)
In some other language - yes. In a language, where such throw specification is enforced statically, like in Java.
While this doesn't matter if your error handling is only one level up, it's going to create a lot of problems in more complex cases because it makes error-neutral contexts impossible and,
Let me be clear: I do not claim that things like `outcome` are superior to exceptions. What I claim is that there happen to be rare ("rare" is very subjective here) situations where things like `outcome` fit better than exceptions.
as the exception specifications fiasco showed,
So yes, I would probably never agitate to put static exception checking into C++ and impose it on everybody. But for solving local problems, or for specific environments, I would like to have a library that offer different error handling trade-offs and is superior to error codes.
in general it is not possible for functions to even "know" what errors might propagate through them.
Agreed.
Assuming we agree that it is not acceptable for error-neutral contexts to kill errors they don't recognize, this is a problem.
Ok. It is just that I have parts of the program that I do not want to be exception neutral by accident. Regards, &rzej;