
Zero-overhead deterministic exceptions: Throwing values [pdf] - ingve
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0709r0.pdf
======
comex
I find it strange that this design forces you to use a single exception type,
std::error, for everything you want to throw with the new mechanism. Even
assuming it makes sense to use a single exception type throughout a given
codebase, as opposed to using more specific per-function error types (which
admittedly has drawbacks, as mentioned in the paper), I should still be able
to define my own single type rather than using the STL's implementation.

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:

    
    
        template<typename Func>
        void foo(Func func) throws(throws(func()))
    

means that calling `foo(func)` can throw iff `func()` can throw. It makes
sense, at some level… I just think it looks janky.

~~~
Rusky
> I find it strange that this design forces you to use a single exception
> type, std::error, for everything you want to throw with the new mechanism.

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.

~~~
comex
The optimized ABI doesn’t depend on there being one error type for everything.
Ideally it should be usable for any function parameter or return of a type
that can handle being moved with memcpy - not just errors. And indeed, in the
current Clang implementation you can stick [[trivial_abi]] on any class.

------
quotemstr
This paper suggests that people avoid C++ exceptions due to some problem with
dynamic allocation or unpredictable cost or something. Maybe that's true in
hard real-time circles, but in my day-to-day experience with C++ exception
avoidance , I see very little rationality going into the decision.

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.

~~~
dbaupp
_> they claim that C++ exception handling produces "undefined" behavior (it
doesn't),_

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.

~~~
quotemstr
There's an element of Nissan Taleb's "Dictatorship of the Small Minority"[1]
at work here.

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.

[1] [https://medium.com/incerto/the-most-intolerant-wins-the-
dict...](https://medium.com/incerto/the-most-intolerant-wins-the-dictatorship-
of-the-small-minority-3f1f83ce4e15#.z5ry4bucq)

~~~
adrianratnapala
In this case though, the dynamic goes both ways. Programmers brought up on
modern C++ will frequently be just as intransigent against error-return values
and other alternatives to exceptions.

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.

~~~
quotemstr
I hope that reasonable people in both camps can agree that blatant contract
violation and assertion failure should result in fail-fast instant death. It's
a sad environment that bans assertions.

------
getpost
This is a step in the right direction. C++ exceptions in practice are
difficult to get right, and depending on dynamic allocation in an exception
seems problematic to me.

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.

~~~
mastax
> 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:

    
    
        int foo();
        std::either<int, std::std_error> foo();

~~~
nine_k
Why not eschew the idea of "exceptions" at all then, and just return an error
outcome if it can be _expected?_ You've just wrote down a way to do that with
no ABI changes.

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.

~~~
mastax
It seems a bit like the difference between

    
    
        async fn foo() -> int
    

and

    
    
        fn foo() -> Async<int>
    

(pseudo-rust syntax but the distinction also applies to C# and JS, et. al.)

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)

------
randyrand
good error/failure framework:

\- 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

------
YouAreGreat
As someone who's not into real-time or zero-overhead, this is still
interesting:

Is it feasible to restrict exceptions to throwing a single simple "error code"
type without losing much of value?

~~~
foota
It still works fairly well for knowing whether something can be recovered
from, but you do lose some context, which needs to be provided in other ways
for things like debugging.

~~~
renox
I wonder if it wouldn't be possible to have 'heisenberg exceptions': if an
exception is caught, it's just a normal exception, but if it isn't caught then
there is a core dump which means you get all the data you need to debug the
issue.

~~~
kjeetgill
This is very similar to how go's panic/recover work. In Java an uncaught
exception will kill just the thread (and usually log the stack trace in the
process).

