Support for exception-less error handling

178 views
Skip to first unread message

Igor Baidiuk

unread,
Jun 29, 2017, 7:39:37 AM6/29/17
to ISO C++ Standard - Future Proposals
There's a rather strange situation with error handling in C++, at least to me.
  • Exception-based error handling is de-facto recommended way. It's relatively ergonomic, allows to pass arbitrary info about error, gives ways to filter exceptions by type. As a price, exceptions have runtime penalty, require compiler support and are not reflected in function signatures - checked exceptions were deprecated long ago, if I'm not mistaken. Also, they're prohibited in some coding conventions (loike Google's) and no-livers in lowlevel development.
  • Exception-less error handling is based mostly on error codes. It works everywhere, requires no special runtime support, and error type (especially if it's a proper enum) is reflected in function signature - which grants more compiler help. As a downside, the code which uses error statuses often contains much boilerplate ("if (there-is-an-error) { handle-error;} "), and error statuses are usually easy to omit and not handle.

What if we had ergonomics for error codes almost equal to exceptions?


The issues with error code easy to not process and not carry any additional info are relatively easy to solve. There was a proposal to add std::expected type, which is a C++ implementation of Either/Result algebraic data type well known in FP world. Such a type would require explicit unpacking to get value and explicit checking if there's an error.


The issue with status checking boilerplate is more complex though. In fact it requires that an expression is allowed to both declare expression-local variable and have return statement:

  1. The result of function call is assigned to some temporary
  2. The temporary is checked if it contains error
  3. If there's an error, error-object is returned early from function
  4. If there's an actual result, that result becomes result of expression

The main problem is that C++ has strict distinction between statements and expressions, and doesn't allow to mix them. Based on this, we need to either allow 'if' statement to be expression in some cases, or allow declaration statement and return statement to be used in ternary operator:


auto result = if (auto&& __tmp__ = func_returning_result(), __tmp__.is_result())
    { std::forward<decltype(func_returning_result())>(__tmp__).get_result(); }
    else { return
std::forward<decltype(func_returning_result())>(__tmp__).get_error(); };

or, with ternary operator:


auto result = (auto&& __tmp__ = func_returning_result(), __tmp__.is_result()
    ? std::forward<decltype(func_returning_result())>(__tmp__).get_result()
    : return
std::forward<decltype(func_returning_result())>(__tmp__).get_error());

Of course, such boilerplate can be hidden behind macro - but "result-or-return expression" doesn't seem to be valid construct in current C++.


Thoughts on this?


Bengt Gustafsson

unread,
Jul 3, 2017, 4:21:14 PM7/3/17
to ISO C++ Standard - Future Proposals
I would doubt that the runtime overhead of exceptions is worse than that of checking return values and continue returning out the call stack. Especially in the no-error case it should be more efficient as those ifs have potentially large runtime penalty regardless of if the jump instruction is taken or not, while the exception unwinding code is typically very asymmetric with a minimized runtime cost for the non-throw case.

So before suggesting another mechanism that simplifies writing code based on error returns I would suggest actually measuring the performance in the throwing and non-throwing case compared to a return value/if-based solution. This article is not totally clear but casts serious doubts about any speed gain from avoiding exceptions:


Unfortunately (or fortunately as it is so old) the TS report linked to does not show numbers for exception handling. Maybe someone else can point to a paper detailing the performance for the different approaches?

Nicol Bolas

unread,
Jul 3, 2017, 4:42:31 PM7/3/17
to ISO C++ Standard - Future Proposals
On Monday, July 3, 2017 at 4:21:14 PM UTC-4, Bengt Gustafsson wrote:
I would doubt that the runtime overhead of exceptions is worse than that of checking return values and continue returning out the call stack. Especially in the no-error case it should be more efficient as those ifs have potentially large runtime penalty regardless of if the jump instruction is taken or not, while the exception unwinding code is typically very asymmetric with a minimized runtime cost for the non-throw case.

So before suggesting another mechanism that simplifies writing code based on error returns I would suggest actually measuring the performance in the throwing and non-throwing case compared to a return value/if-based solution. This article is not totally clear but casts serious doubts about any speed gain from avoiding exceptions:


Unfortunately (or fortunately as it is so old) the TS report linked to does not show numbers for exception handling. Maybe someone else can point to a paper detailing the performance for the different approaches?

You should really look at the SG14 forum and their various papers. They've made measurements, looking at both the cost of the non-exception case and the cost of the exceptional case.

Furthermore, there are different types of users of C++. For some, having a consistent cost is more important than being faster most of the time but really slow if something exceptional happens. And consistency of performance can matter more in specific places than others.

Overall, I think a pretty decent case has been made that having a functional alternative to exceptions, whether in the language or the library, would be a good thing. The most pernicious case is the difficulty of signaling errors from constructors; such a thing fundamentally requires a language-level solution (or to write constructors that can't fail).

Brittany Friedman

unread,
Jul 3, 2017, 4:44:38 PM7/3/17
to std-pr...@isocpp.org
On Mon, Jul 3, 2017 at 3:21 PM, Bengt Gustafsson <bengt.gu...@beamways.com> wrote:
I would doubt that the runtime overhead of exceptions is worse than that of checking return values and continue returning out the call stack. Especially in the no-error case it should be more efficient as those ifs have potentially large runtime penalty regardless of if the jump instruction is taken or not, while the exception unwinding code is typically very asymmetric with a minimized runtime cost for the non-throw case.

So before suggesting another mechanism that simplifies writing code based on error returns I would suggest actually measuring the performance in the throwing and non-throwing case compared to a return value/if-based solution. This article is not totally clear but casts serious doubts about any speed gain from avoiding exceptions:


Unfortunately (or fortunately as it is so old) the TS report linked to does not show numbers for exception handling. Maybe someone else can point to a paper detailing the performance for the different approaches?


There are plenty of reasons to use error codes or error-code-like-entities that are not motivated solely by performance considerations.

Some users find error codes easier to audit and work with (no hidden control flow).
Some users are on platforms that recommend against or forbid the use of exception handling. The platform vendor in question does not provide a high-quality exception handling mechanism.
Some users don't want to pay for the *memory* overhead of the so-called """""""""zero cost""""""""" (there are not enough scare-quotes in the world) mechanism.
Exceptions do not compose well (see the now defunct exception_list)
If we presume that exceptions can in *some* cases be slower, then if "errors" are very common or if you require *predictable* performance characteristics, exceptions may be unacceptable.

Nicol Bolas

unread,
Jul 3, 2017, 4:50:07 PM7/3/17
to ISO C++ Standard - Future Proposals

Neither do error codes. Generally speaking, two sets of error codes from two different libraries cannot be composed without creating a list of error codes.

If we presume that exceptions can in *some* cases be slower, then if "errors" are very common or if you require *predictable* performance characteristics, exceptions may be unacceptable.

That's a "performance consideration".

Brittany Friedman

unread,
Jul 3, 2017, 4:51:46 PM7/3/17
to std-pr...@isocpp.org
On Mon, Jul 3, 2017 at 3:50 PM, Nicol Bolas <jmck...@gmail.com> wrote:

If we presume that exceptions can in *some* cases be slower, then if "errors" are very common or if you require *predictable* performance characteristics, exceptions may be unacceptable.

That's a "performance consideration".

Thank you for this enlightening feedback.

Tim Teatro

unread,
Jul 3, 2017, 10:43:21 PM7/3/17
to ISO C++ Standard - Future Proposals
I have nothing to add on the discussion of using (or not using) exceptions. I have no dog in that fight, but I do sometimes work in an areas where exceptions are not an option.

If the decision is made to not use exceptions, then a solution to your boilerplate problem is a monad. Monads (which you can think of as a design pattern) make automatic a lot of the boilerplate that surrounds working with container or embellished types. In your case, you want a container which has your function's return value AND/OR a status code and you don't want to have to deal with the boilerplate of packing, unpacking and checking.

The simplest case of this sort of pattern is called the "maybe monad", and is easily built around std::optional. An optional can contain a value (or not), allowing failure, but you get no error code. It's not exactly what you want, but it's easier to understand. Check out this portion of Phil Nash's talk describing the std::optional based "maybe" monad ( https://youtu.be/8hW-LT8qFT0?t=51m2s ). If you feel like this solution is right for you, and you want to kick it up a notch and get error codes, I'll help you.

Igor Baidiuk

unread,
Jul 5, 2017, 2:27:32 AM7/5/17
to ISO C++ Standard - Future Proposals
I'm well-aware of monadic approach to this problem.
If you use single monadic call chain as function body, it would become much less readable, and simple control flow can be of an issue.
Otherwise, you'll end with if-else checks anyway.
Anyway, I saw some examples. They looked not very fitting for everyday work.

gmis...@gmail.com

unread,
Jul 6, 2017, 4:15:08 AM7/6/17
to ISO C++ Standard - Future Proposals
So the op is proposing some kind of return early statement? Like this:

enum class open_file_status
{
    ok, file_not_found, file_locked, file_no_permission
};
struct open_file_result
{
    file_handle fh;
    open_file_status status_code;
   
    file_handle& value() {  return fh; }
    bool has_value()  {  return status_code == open_file_status::ok;  }
    get_status()  {  return status_code;  }
};

// If use like this (note new ?= syntax)
bool do_something_with_file()
{
    file_handle fh ?= open_file("not_there.txt") -> false;
}

// ?= is transformed to something like this:
bool do_something_with_file()
{
    auto __tmp{open_file("not_there.txt")};
    if (!__tmp.has_value())
        return false;
    // returns object after -> otherwise get_status if present, otherwise __tmp
    file_handle fh = __tmp.value();
}

Why not offer something like that if that's what people want to use?
Reply all
Reply to author
Forward
0 new messages