C++'s (mostly) superset of C implies that it's possible to handle exceptional cases without the widespread use of exceptions as C does, so only the C++ features which require exception use affect the use of exceptions. Furthermore, any function or method whose interface includes exceptions can always be wrapped to catch and repackage as a Result or equivalent. It's these two antecedents that result in the consequences that
1. Under no circumstances, should program code be throwing an exception; and
2. Any exceptions thrown of interfaces not under 1st party control should be immediately caught and repackaged.
The result is a unified theory of error handling that don't rely on weird no true Scotsman social conventions of what is "truly exceptional" or not or the presence of multi parallel error handling systems. Results and the like respect lawful composition which is orders of magnitude more comprehensible than any exception-based practice even exception exceptions.
Exceptions were added after CFront 2.0[1] but before/concurrent to the Annotated C++ Reference Manual[2] release which served as the de facto standard until the ISO[3]. The Annotated C++ Reference Manual itself references "Andrew Koenig and Bjarne Stroustrup: Exception Handling for C+ (revised), Proc. USENIX C+ Conference, San Francisco, April 1990."[4]
In fact there is a hint at the design considerations in the latter "an implementation that has no run-time costs when exceptions do not occur."
Perhaps a question to Rust users, is Result zero-overhead? The answer to that may be the same.
The C++ Reference Manual continues:
> The design here and the criteria for it evolved slowly. An earlier version of the design was presented at the ‘C+ at Work” conference in November 1989 by Andrew Koenig and Bjame Stroustrup. The design presented here owes much to the discussion generated by that paper and in particular to comments by Toby Bloom, Peter Deutch, Ken Friedenbach, Keith Gorlen, Michael O'Riorden, Mike Powell, Jim Mitchell, Rob Seliger, Jonathan Shopiro, and Mike Tiemann. The design is clearly influenced by the exception handling mechanism in ML.
Another important source of inspiration was the work on fault-tolerant systems done in the University of Newcastle in England under the direction of Brian Randel.
Code that doesn't return Result will have no runtime cost added just like code that doesn't have a throw statement doesn't.
Code that returns Result but is always Ok will have a very small overhead just like code that can't provably never throw will. But I'd wager it's extremely small given that on modern CPUs branches never taken are much cheaper than they were in 1990.
To put it in more plain terms, the reason that C++ exceptions have "no overhead" when an exception occurs is that the exception handler code will only be dispatched when an exception is raised. The same is true of error handlers in Rust. No Err, no handler called. The difference is the branch is always there. So the question in 2022 is, how expensive is a branch that is never reached? Today it is basically nil, depending on your hardware. Branch predictors are pretty good and pretty ubiquitous (nb4 someone comes talking about how this wouldn't work on their Blackfin ADSP or whatever - neither will exceptions!).
There is a small overhead though in that there are two branches. The other nuance is stack unwinding. But in practice this is generally a good thing in debug builds that you can disable in release if you want to.
At the end of the day, the practice is for users not to write or call code that may error or propagate errors when the utmost performance is required. Inputs get validated up front and fail fast so the hot loops maximize performance.
> Assert usually throws an exception in most environments.
Not really. In C and C++, an assert() calls std:abort. In custom implementations it does whatever you'd like it to do. I've seen assert() call asm('int13;') out of convenience. I never see an exception once.
...in debug configurations. And compiled out in release configurations. This makes it useless for checking user input. (OK for a library code, when input is validated higher in the stack.)
1. Under no circumstances, should program code be throwing an exception; and
2. Any exceptions thrown of interfaces not under 1st party control should be immediately caught and repackaged.
The result is a unified theory of error handling that don't rely on weird no true Scotsman social conventions of what is "truly exceptional" or not or the presence of multi parallel error handling systems. Results and the like respect lawful composition which is orders of magnitude more comprehensible than any exception-based practice even exception exceptions.