App-level Developer on std::error Exceptions Proposal for C++. Part II. The Discussion.

Author:  Follow: TwitterFacebook
Job Title:Sarcastic Architect
Hobbies:Thinking Aloud, Arguing with Managers, Annoying HRs,
Calling a Spade a Spade, Keeping Tongue in Cheek

DISCLAIMER #1: This post is about C++ proposal; it is not a part of the language, and won’t be implemented for a while. Moreover, ALL the details may change in any way, up to their exact opposites.

DISCLAIMER #2: I am NOT a member of WG21, so everything here is merely musings of somebody from outside. OTOH, I am sure that exactly because of being an outside view, these musings can be useful. 

Summary of Part I (The Good)

For quite a long while, in certain parts of C++ community, there is a substantial resistance to existing C++ exceptions; this leads to an alternative subculture of using error codes instead of exceptions, so there are effectively two barely-compatible C++ worlds: world-using-exceptions, and world-using-error-codes.

Existing C++ exceptions have two separate problems:

  • performance when an exception is fired (strictly speaking, it is not only performance, but the difficulty to provide any estimates for such performance)
  • lack of explicit markers for “which of the functions can throw exceptions”. This, in turn, leads us to the need to treat everything as potentially-throwing, which increases the costs of making our code exception-safe multiple-fold (and often leads to ignoring exception safety altogether <sad-face />).

Very recently, Herb Sutter introduced a formal WG21 proposal named [P0709R0], which addresses both these problems.

In [P0709R0], a new type of exceptions is proposed – std::error exceptions. For these new exceptions (which look exactly as normal exceptions at app-level) well-controlled performance for all the execution paths is ensured via restricting our new exceptions to one single class std::error, which happens to be very small (2 pointers), and as such can fit into CPU registers (especially on platforms with lots of registers, including x64). While having one class instead of current very generic exception hierarchy formally IS a restriction, from my experience I do NOT see any practical issues1 even if switching ALL my exceptions to std::errors (that is, IF certain points discussed in the “On std::error” section below will stand).

As for the making potential-exception-points explicit and self-documented, P0709R0 proposes try exceptions; I DO like it too (at the very least – it is close to the very best possible proposal to make exception handling explicit, so if this won’t fly – nothing will).

Overall, I LOVE the P0709R0 proposal; however, as it has certain options and certain things still left to our imagination, I want to comment on my preferences for them <wink />

1 that is, for a new project so we can rule out migration costs


On std::error

//NB: as Ben Craig has pointed out, there is even more recent proposal for std::error in [P1028R0]; however, I see it as mostly-orthogonal to the wishes expressed below; in particular, I didn’t manage to find handling of user-defined extra data in [P1028R0].

As std::error is at the core of the proposal, it certainly deserves a LOT of attention – and in R0 of P0709 (unless I missed something) it is IMO under-defined. In particular, as app-level developers (at least me and a few of my app-level colleagues) we’d clearly like for std::error to have the following features:

  • whenever existing C++ exception is converted to std::error, then depending on the nature of our C++ exception class, we would like to have two options:
    • to say that this exception is nothing but error code, so it is converted into an std::error with a certain domain field (let’s name it class_exception_error_code), and payload field equal to the error code.
    • to say that we want to preserve this exception class with all the associated data; to do it, we can create an on-heap copy of our exception class, and to create an std::error with a different domain field (let’s name it class_exception_allocated). For such class_exception_allocated exceptions, the destructor of std::error will deallocate the data from the heap (hence the need to have non-trivial destructor for std::error).
      • As an implementation detail, we will be ok to say that all such existing-C++-exceptions-to-be-converted are derived from std::exception
  • Hare pointing out:having an ability to associate extra information with std::error is important for us - both for our own std::error exceptions, and for conversion from existing C++ exceptionsWe would appreciate another domain for our own purposes – say, user_error, and with our own handling for this domain within std::error destructor.
  • In other words, having an ability to associate extra information with std::error is important for us – both for our own std::error exceptions, and for conversion from existing C++ exceptions. Which in turn means that I am arguing for going beyond [P0824R1] (but along the lines of 4.6.4 of P0709R0), and DO specify a way to extend std::error.
  • even better, we’d like to have an option to register our own domains – with their own handling – but if it becomes a headache, we’ll settle for those three domains mentioned above, being pre-defined.

Now, I will be THINKING ALOUD about the ways how those-things-above can be implemented; don’t take it as something smart – all chances are that it is not <wink />.

Keeping in mind [P1028R0], what I am still missing is the following:

  • a hook set_std_error_user_destructor – with handler taking (user_domain, void* payload) as parameters, and handling ALL user-defined domains (I heard that [P1028R0] does provide this functionality, but I didn’t manage to find it yet)
  • another hook set_std_exception_to_error_conversion() with the hook taking std::exception& x (and using make_user_std_error() to achieve whatever-is-desired).


On Heap Exhaustion

One of the extensions of [P0709R0] proposes to treat heap exhaustion in a special way. My response to it goes as

Yes, please please DO treat heap exhaustion separately – with a cherry on top!

In quite a few of my projects, I’ve been into a situation when somebody comes to me and says “hey, if this allocation in this constructor fails – we won’t delete another piece of memory, so we’re not exception safe, we should do something about it”, with my reply almost-universally being “For this particular project, if 20-byte allocation fails, we’re long long dead. Which means that spending time on handling this situation (which in turn will make our code more convoluted, less readable, and will risk introducing a bug in the process) doesn’t make sense. However, make sure to keep in mind that ALL the other exceptions – AND some of larger allocations – still HAVE to be handled in an exception-safe manner.”

Or looking at the very same thing from a different angle:

Without treating heap exhaustion separately, LOTS of programs out there will never be formally exception-safe – and worse, LOTS of developers won’t even try to introduce exception safety into their programs2.

Which means that by allowing to handle heap exhaustion in a special manner (~=”it should never happen”), we’ll be increasing efforts community is ready to spend on exception safety (which is a Good Thing(tm)).

That being said, I am not sure that I like proposal of [P0709R0] on how to treat heap exhaustion. In particular:

  • [P0709R0] proposes to allow implementations to declare heap exhaustion as an UB. In general, given the experience with compiler writers abusing UB (see signed UB overflow and calling never-called functions), I hate when any new UB is introduced into the standard (it opens a door for formally correct but crazily-dangerous “optimizations” – and it is not the compiler writers who are to blame for these dangers, but WG21 which is taking UBs waaaay too easily).
    • Arguing hare:I think that current C++ standard is Badly Lacking(tm) a concept of 'segfault' (which is supported by CPUs on VAST majority of modern systems with modern C++ compilers)Overall, I think that current C++ standard is Badly Lacking(tm) a concept of “segfault” (which is supported by CPUs on VAST majority of modern systems with modern C++ compilers) – and that it is segfault exception (see also below re “bad”/”unchecked” exceptions) should be the preferred implementation-defined option for Linux-like systems here (and in quite a few other places around the standard – including dereferencing null pointers and allowing to prohibit behaviour which causes that (in)famous compiler-calling-never-called-function problem).
    • AT LEAST, there should be an option to implementation-define heap exhaustion as terminate() (easily implemented at zero cost on VAST majority of modern CPUs – and is MUCH better than UB).
  • I would clearly like to have an option to specify a system-wide callback which decides what to do on heap exhaustion. One of the options at this point should be to throw an std::error – however, with an understanding that no exception safety is guaranteed by standard library for this kind of exceptions.
    • <thinking-aloud>More generally, I would probably consider splitting all the std::errors into two categories: “good” exceptions (which ARE guaranteed to cause function to be declared as throws, guaranteed to be handled via try expressions etc.) and “bad” exceptions (which DO happen and DON’T cause UBs but are handled without any exception safety guarantees; I’ve seen LOTS of real-world projects where this middle-ground-between-perfect-safety-and-perfect-failure has saved somebody’s bacon, more on it in Part III of this mini-series).
      • FWIW, a similar model exists in Java (as Exception being “good”/”checked” and RuntimeException being “bad”/”unchecked”) and from what I’ve seen, it does work pretty well; in a sense – “unchecked” exceptions provide that necessary middle-ground between perfect-safety and perfect-failure. IMNSHO, recognizing the existence of this middle-ground is even more critical for closer-to-hardware C++ than for Java. More on checked/unchecked exceptions in upcoming Part III of the mini-series.</thinking-aloud>

2 or are giving up on exception safety completely after realizing the effort necessary to handle all the small allocation failures


Static Enforcement of try Expressions

As I mentioned before, I DO like an idea of potential exception points being very clearly documented in the code (that is, IF we care about exception safety). On the other hand, I DO recognize (a) that there are LOTS of app-level projects out there which don’t really care about exception safety (and are still working ok), and (b) that migration to this IMO-brighter future won’t be easy <sad-face />.

Assertive hare:compile-time enforcement of calls-to-all-throwing-functions-being-marked-with-try SHOULD NOT be an error (and SHOULD be a warning instead)One of potential proposals in [P0709R0] (see section 4.5.2 there) is to enforce at compile-time that WITHIN the function-marked-as-throws, all expressions which MIGHT throw, MUST be explicitly marked as try-expressions. At this point, I would Weakly-Favor this suggestion, with one all-important clarification – this compile-time enforcement of calls-to-all-throwing-functions-being-marked-with-try SHOULD NOT be an error (and SHOULD be a warning instead). This way, we’ll have a lot of leaway in how-we-handle-migration-at-app-level (up to the point of “this project will stuck without static enforcement forever-and-ever”), but OTOH, mere existence of any warning is known to be a rather strong incentive towards eliminating it – which in our case will translate into an incentive towards “better and brighter future of all the expressions becoming explicitly marked”. Also, while I do like the idea of explicit marking on the first glance, without the real-world evidence it is impossible to be sure that it does fly in practice; and IF it appears that explicit try-marking is not a good thing for most of the projects – it will be easy to deprecate this warning (which will effectively lead to excluding from -Wall-or-something in compilers).

On Contracts

Effectively, a prerequisite to moving towards brighter-and-better future of exception handling, is contracts; this is especially true if we’re aiming to introduce static enforcement along the lines above. One of the problems with modern exception handling is that these days, effectively, VAST MAJORITY of the functions can throw (either because of asserts, or because of allocations which can lead to bad_alloc). As a result,

Reducing number of potential exception points is of paramount importance IF we want app-level developers to aim for exception safety

As a result, I am strongly supporting BOTH contract-violations AND heap exhaustions being-treated-as-NOT-a-(checked)-exception (that is, for the purposes of the code, exception safety, and static analysis, though not necessarily for production runtime, more on it and on the concept of unchecked exceptions in Part III of this mini-series).


Syntactic Sugars

In section 4.5.3, [P0709R0] proposes some syntactic sugars for catching.

The first such proposed sugar allows to drop try{} completely, implying it whenever catch is used, and saving on the verbosity (example from P0709R0):

//FROM P0709R0:
int main() {
  auto result = try g(); // can grep for exception paths
  cout << “success, result is: ” << result;
  catch { // clean syntax for the efficient catch
    cout << “failed, error is: ” << err.error();

I have to say that I do NOT really like this syntax (IMO it feels way too counterintuitive for a C++ syntactic culture where implicit stuff is not common to say the least; this applies BOTH to the creation of implicit block, AND to the implicit introduction of identifier err). OTOH, I DO agree with the need to simplify syntax for common use cases. <thinking-aloud>However, IMO existing syntax of “function try-block” is sufficient for this purpose:

int main() try {
  auto result = try g(); // can grep for exception paths
  cout << “success, result is: ” << result;
catch(error err) { // clean syntax for the efficient catch
  cout << “failed, error is: ” << err.error();

Verbosity-wise it is about the same, but IMO it is more straightforward and more obvious for somebody-who-did-not-read-about-this-feature-before (and while it doesn’t allow to simplify catch-in-the-middle-of-the-function, I’d argue that such catches are not that common3 so we can use good-old-syntax for it). </thinking-aloud>

The second proposed syntactic sugar allows to replace

catch (error e) {
  if (e == codespace::something) {
    // do something different, else allow to propagate
  else throw;

with an equivalent

catch (codespace::something) {
  // do something different, else allow to propagate

Again, it seems to imply that identifier err will be introduced implicitly; and again, I do NOT like it (IMO, it is way too counter-intuitive for existing C++ culture). <thinking aloud>Instead, I’d consider something a bit more verbose but more explicit, for example:

catch (codespace::something err) {
  // do something different, else allow to propagate

This way, it won’t introduce identifiers implicitly, and will allow to keep verbosity in check, while keeping familiar catch semantics of “we catch err matching whatever-preceeds-it”. With this modification – AND provided that it will be handled in a consistent manner (including allowing multiple handlers for different somethings), I would really like to avoid verbosity this way.</thinking aloud>

3 at least from my experience, though if somebody has stats for real-world app-level code it would be nice to hear


To Be Continued…

Tired hare:It was the second part (“The Discussion”) of this mini-series on my impressions of std::error exception proposal. The third part (“The Non-Addressed: Soft Failures and Unchecked Exceptions”) is scheduled to come in a week, so stay tuned! <wink />

Don't like this post? Comment↯ below. You do?! Please share: ...on LinkedIn...on Reddit...on Twitter...on Facebook


Cartoons by Sergey GordeevIRL from Gordeev Animation Graphics, Prague.

Join our mailing list:

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.