Document number:       N2828=09-0018
Date:                            2009-02-05
Project:                        Programming Language C++, Library Working Group
Reply-to:                      Beman Dawes <bdawes at acm.org>
 

Library Support for Hybrid Error Handling (Rev 1)

Introduction

This proposal provides standard library clause 17 support for functions that allow users to choose whether errors should be reported via exception or via std::error_code object.

The impact is to ensure that a function specified like this:

void f(error_code& ec=throws());

acts as if it had been specified like this:

void f();                // throws on error
void f(error_code& ec);  // never throws

The benefit of a single signature providing both throwing and error reporting semantics is that it eliminates a doubling of the number of signatures, yet allows very efficient implementations. This is particularly beneficial where a function already has an uncomfortably large number of overloads. The intent is that this defaulted argument approach be applied uniformly throughout the standard library because it is less confusing if all hybrid error handling interfaces are consistent.

The proposal makes no change to any current standard library components. It just adds material to clause 17 specifying the semantics implied by an argument specified as error_code& ec=throws().

The original plan was to hold this proposal for TR2, where libraries like filesystem will make extensive use of this error handling idiom. A C++0x use (LWG issue 935, clock error handling needs to be specified) has now arisen, so it may be desirable to add it for C++0x.

Revision history

Rev. 1 == N2828 Changed default argument specification from unspecified name to a function call, thus allowing users to use the idiom in their own code and giving implementers a mechanism to detect (and presumably report) misuse.

Rev. 0 == N2809  Initial proposal.

Motivating example

Each of the three clocks specified in 20.9.5 Clocks [time.clock] provides the member function:

static time_point now();

The semantics are specified by 20.9.1 Clock requirements [time.clock.req], which as of CD1 makes no mention of error handling. Thus the function may throw bad_alloc or an implementation-defined exception (see [res.on.exception.handling] paragraph 4). Since the function is sometimes used in cases where exceptions are not appropriate or where the specifics of the exception or cause of error need to be available to the user, further options are needed.

Indeed, this is an example of a class of errors where use cases require that three possible semantic actions be supported:

This is the most appropriate default behavior, since it covers the very common use case where errors are not expected and the user does not wish to or have the knowledge to code anything dealing with error handling. Because the exception hierarchy is specified to derive from system_error, information about the cause of an error is provided should an exception occur.

For clocks, this semantic action needs to be provided by now() because it is often used in cases where errors are truly exceptional and if they do occur are best handled further up the call chain. Thus this option does need to be available for clock users and is the appropriate default.

This allows users to process errors, including details, yet does not incur the runtime and code complexity cost of try/catch blocks. Without this option, users report code becomes so littered with try/catch blocks that it is unreadable and unmaintainable.

For clocks, this option is particularly attractive for code that must be robust across a wide variety of platforms, including embedded and legacy systems.

This is just a subset of reporting via error_code object; the object is simply ignored. It is particularly appropriate in destructors.

now() is often used in destructors, so having this semantic action makes life simpler for clock users.

The proposed wording gives the user the choice of these three semantic actions on an error, yet does not require a doubling or tripling of the number of function signatures that must be specified in the standard or in actual interfaces. Applying this to the clock example, the now function specification becomes:

static time_point now(error_code& ec=throws());

Implementations are permitted to actually implement this as one or two signatures.

Implementation experience

The general idea of hybrid error handling has been in use by several Boost libraries for several years. The specifics of this proposal have been implemented and tested internally, and are scheduled for the next public Boost release..

Proposed wording

After 17.6.5.12 Value of error codes [value.error.codes], add a new subsection:

17.6.5.13  Semantics of error_code& ec=throws() arguments  [semantics.throws]

Certain standard library functions are specified as having an argument error_code& ec=throws().

Such functions shall behave as if specified as two separate signatures:

A function signature without an error_code& argument. If an error occurs, this function shall throw an exception of type system_error, or of a type derived from system_error, whose code() member function shall identify the reason for the error.

A function signature with an error_code& ec argument. This function shall not throw exceptions. Unless otherwise specified, it shall have these implicit additions to its elements:

Requires: &ec != &throws(). [Note: this precondition allows more efficient implementation and allows implementations to detect misuse. -- end note]

When the Boost implementation added deliberate reference poisoning to detect precondition violations, several silent bugs in Boost Filesystem Version 3 code became noisy test failures.

Postconditions: If an error occurs,  ec.value()and ec.category() identify the error specifics. Otherwise, !ec is true.

Returns: If an error occurs, the return value (if any) is unspecified.

Whether or not implementations actually provide two signatures is unspecified.

At the end of  Header <system_error> synopsis in 19.4 System error support [syserr], add:

error_code& throws();

After 19.4.1.5 Error category objects [syserr.errcat.objects], add a new subsection:

19.4.1.5 Error code objects [syserr.errcat.objects]

error_code& throws();

Returns: A reference that can be used as a default function argument to signify an exception should be thrown when an error is detected ([semantics.throws]).

Acknowledgements

Howard Hinnant suggested use of an unspecified name rather than a specific name to denote the default case, and thus discourage misuse. He also provided other helpful suggestions and comments that have been incorporated into this proposal. Although the unspecified name approach was abandoned because it didn't support use in user written libraries, it led to approach taken by the final proposal.


Revised February 05, 2009

© Copyright Beman Dawes, 2008

Distributed under the Boost Software License, Version 1.0. (See file LICENSE_1_0.txt or  www.boost.org/LICENSE_1_0.txt)