On Mon, Jun 19, 2017 at 11:58 PM, Andrzej Krzemienski via Boost < boost@lists.boost.org> wrote:
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 <
1. I want to separate resource acquisition errors (exceptions are still thrown upon memory exhaustion) from input validation.
Why?
...
I do not even treat validation failure as
"error". But I still like to have the "short exit" behavior of errors.
If it's not an error then it is not an error -- and you should not treat it as such.
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)
Yes, std::logic_error is another embarrassment for C++. Logic errors by definition leave the program in an undefined state, the last thing you want to do in this case is to start unwinding the stack. You should use an assert instead.
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".
Exceptions are not used in case of "irregularities" but to enforce postconditions. When the program throws, it is in well defined state, working correctly, as if the compiler automatically writes "if" statements to check for errors before it executes any code for which it would be a logic error if control reaches it. The _only_ cost of this goodness is that your code must be exception safe. Programmers who write debuggers that by default break when a C++ exception is thrown likely do not understand the semantic differences between OS exceptions (e.g. segfaults, which *do* indicate logic errors) and C++ exceptions. Semantically, that's like breaking, by default, every time a C function returns an error code.
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`.
That's because (in your mind, as you stated) you're not using Outcome to handle "real" errors.
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
Not really, if you forget to check for errors and call .value() on the outcome object, it'll throw (if I understand the outcome semantics correctly). That exceptions are propagated if you forget to handle them when you should is a good thing. It means no error gets ignored.
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.
I personally think that libraries are definitely needed when they can deal efficiently with 97% of all use cases, the remaining 3% being not nearly as important. Evidently we disagree.
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.
It's a bad idea. Again: generally, functions (especially library functions) can not know all the different kinds of errors they might need to forward (one way or another) up the call stack. From https://herbsutter.com/2007/01/24/questions-about-exception-specifications/: "When you go down the Java path, people love exception specifications until they find themselves all too often encouraged, or even forced, to add throws Exception, which immediately renders the exception specification entirely meaningless. (Example: Imagine writing a Java generic that manipulates an arbitrary type T…)"
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.
You mean bugs where exceptions aren't handled when they should be, which in C++ may result in std::terminate, which seems harsh. But these bugs are also possible when errors aren't reported by throwing, the result being that they're neither handled nor propagated up the call stack -- in other words, they're being ignored. I submit that std::terminate is much preferable outcome in this case (no pun intended).