Also, the idiom `throws(throws(foo))` feels like janky syntax. To explain, for anyone who hasn't bothered to read the proposal: you can use use `throws` in two different places with different meanings. Tacked onto a function declaration, `throws(COND)` means the function can throw depending on the value of COND, which should evaluate to a value of type `enum except_t`: one of no_except, static_except, or dynamic_except. But as an expression, `throws(EXPR)` does the reverse: it evaluates to an `enum except_t` representing whether/how evaluating `EXPR` can throw. So:
void foo(Func func) throws(throws(func()))
I actually kind of like the idea. It ensures that error returns will be a fixed, small size, which enables the proposed ABI changes; and allows for attaching arbitrary information through std::exception_ptr, which avoids the need for checked exceptions' proliferation of error types.
I am continually frustrated by people who refuse to use C++ exceptions. These people claim to have a "philosophical objection" to the "unusable" exception model. They continually cite "bad" exception code involving try-catch clauses at every stack frame (which nobody actually writes), they claim that C++ exception handling produces "undefined" behavior (it doesn't), or that compiler support is somehow buggy (it isn't). These arguments make no sense. They have no persuasive power. They do not intersect with the real world.
Best of all, these very serious people, having written very sagacious emails banning random parts of C++ and having intoned that horrible things happen if one allows non-local flow control, switch from their email clients to their editors and go on to write Python and Java! Somehow, exceptions are cripplingly unusable in C++ yet not worth writing home about when used in Python or Java.
My theory is that most opposition to C++ exception usage is motivated mostly by cargo-culting and that most of the supposed arguments against exceptions are post-hoc rationalizations of a decision already made on the basis of non-technical factors. To wit: there were real performance and correctness problems with exceptions in early 1990s C++ compilers. These problems have been resolved for a very long time. Yet the 1990s meme that to be a responsible programmer, one should disable exceptions, or at best "use exceptions for exceptional situations" comes down to us in the present day. Too often, people blindly follow advice without seriously considering it, and it seems more likely that random bans on C++ exceptions demonstrate this phenomenon than any legitimate technical disagreement.
Exception safety (i.e. ensuring state is transactional) is hard in any language, but it's extra-hard in C++ where doing it incorrectly can lead to undefined behaviour rather than just an incorrect program. It's true that all of those sorts of problems with exceptions can occur just using normal `return`, but the hidden control flow of exceptions aggravates the problem by making it much harder to identify places of concern.
> Best of all, these very serious people, having written very sagacious emails banning random parts of C++ and intoning that horrible things happen if one allows non-local flow control, switch from their email clients to their editors and go on to write Python and Java! Somehow, exceptions are cripplingly unusable in C++ yet not worth writing home about when used in Python or Java.
This seems unreasonable. In general, people aren't nearly as demanding about things like performance for tasks they're solving with Python and Java as with C++. Additionally, I'm sure a significant chunk of those who dislike exceptions in C++ also dislike them elsewhere, but there's a variety of reasons that they're using any of those languages despite that (an inherited codebase, library support, ...).
> To wit: there were real performance and correctness problems with exceptions in early 1990s C++ compilers. These problems have been resolved for a very long time.
Clang, a C++ compiler, and LLVM, its optimizer back-end, don't use exceptions. While there are probably a variety of non-technical factors also coming into play, it seems hard to swallow that literal C++ compiler writers would entirely cargo-cult a major choice like this.
If you bring together a random group of C++ programmers, some of them will approve of exceptions and some of them will see exceptions as an abomination. The former group will grudgingly write non-exceptional code; the latter group will not write exceptional code (at least in C++). Consequently, the codebase ends up banning exceptions, the fact of which the anti-exception people use as evidence for more exception bans in other projects.
This dynamic doesn't speak to the validity of the argument against exceptions. It just indicates that it's usually easiest, socially, to just appease the anti-exception people.
In my own experience, exceptions are useful because real programmers can't be trusted to handle errors at all. So it is better to crash the program than to allow silent corruption. And exceptions are more politically palatable than assertions.
But if the quality of your team AND processes are high enough you can expect errors will actually be handled, then it is easier to do that correctly without exceptions.
One of the main reasons to use exceptions is to save code. In one of my projects, using exceptions instead of checking error returns saved 11% in code size. The exception version of the code was more correct, since there were many fewer places where error codes were handled. The golang people just don't get this.
The subtleties are not longer fresh in my mind, but "throws" rubs me the wrong way. Why declare whether a certain function may throw an exception? The beauty of exceptions is that you can use code that throws exceptions without having to know exactly where exceptions can occur.
Throwing an exception does not alter the ABI for a function, since the mechanism for throwing exceptions is separate from normal control flow. They call into library code for unwinding the stack. This is what makes them "slow", and it's why they require heap allocation since there's no pre-made place to store the value.
Throwing a value (as proposed) does alter the ABI for a function. That value needs a place to go, it's essentially another return value. Removing the requirement to add throws to the declaration would be like allowing these two functions to be the same:
std::either<int, std::std_error> foo();
A more expensive (and preferably non-allocating) mechanism could be used for a controlled "panic", that is, winding things down in an orderly manner when an unpredictable error occurs.
async fn foo() -> int
fn foo() -> Async<int>
Async code and error handing can both be implented with regular code, but there are cases where some compiler magic can be more elegant, more expressive, or more performant. In these cases it's nice to have a construct that distinguishes this magic from regular values, rather than just making a specific return type magic, IMO. In the throwing values proposal, the magic is to make a regular value usable in exceptions, and also to use mostly-hidden CPU state (flags register) to indicate the type of the value. This allows the C++ proposal to use less memory and perhaps be more performant than rust-style error values. (This ABI change could also be used in other languages like Rust, as mentioned at the end of the document)
- should be obvious which functions can fail and what errors they can fail with
- propogating an error up the stack should be visible in the code and semantically easy to do
- errors should be composable and easy to add additional information to
- deciding to not handle a error should be explicit and otherwise produce a warning
- errors should be fast and cheap to return errors
- (maybe nice to have) ability for a function to have and return multiple errors
Is it feasible to restrict exceptions to throwing a single simple "error code" type without losing much of value?