Deprecating Exception Specifications

Author: Doug Gregor
Contact: doug.gregor@gmail.com
Date: 2010-03-12
Number:N3051=10-0041

index

Introduction

UK-136
Exception specifications have proven close to worthless in practice, while adding a measurable overhead to programs. The feature should be deprecated. The one exception to the rule is the empty throw specification which could serve a legitimate optimizing role if the requirement to call the runtime unexpected mechanism was relaxed in this case.

As expressed in the national body comment above, exception specifications have not proven useful in practice. There are numerous discussions of the problems with exception specifications in C++ (see, e.g., [Sutter02], [Boost03]), but the main issues are:

In practice, only two forms of exception-throwing guarantees are useful: an operation might throw an exception (any exception) or an operation will never throw any exception. The former is expressed by omitting the exception-specification entirely, while the latter can be expressed as throw() but rarely is, due to performance considerations.

[N3050] introduces a new kind of exception specification, noexcept, the specifies that the function will not throw any exceptions. Unlike throw(), noexcept does not require the compiler to introduce code to check whether an exception is thrown. Rather, if a function specified as noexcept is exited via an exception, the result is a call to std::terminate().

With the introduction of noexcept, programmers can now express the two kinds of exception guarantees that are useful in practice, without additional overhead. This paper therefore proposes to deprecate "dynamic" exception specifications, i.e., those that are written as throw(type-id-listopt).

Approach

To aid in the transition from dynamic exception specifications to noexcept, the wording provides somewhat loose compatibility rules for redeclarations of functions that have exception specifications. Two rules stand out:

1) All "non-throwing" forms of exception specifications (throw(), noexcept, noexcept(true)) are considered compatible, but the exception specification on the definition is what affects code generation:

// header ultramodern.h
void f() noexcept;

// source plodding.cpp
#include "ultramodern.h"
struct X { };
void f() throw() { // okay, compatible with noexcept
  throw X(); // calls std::unexpected()
}

2) noexcept(false) is considered compatible with throw( type-id-list ):

// header ultramodern.h
void g() noexcept(false);

// source plodding.cpp
#include "ultramodern.h"
struct X { };
void g() throw(X) { // okay, compatible with noexcept(false)
  throw X(); // okay
}

These compatibility rules allow a gradual migration from dynamic exception specifications to noexcept, since a declaration of a function can choose to use the new or old syntax independently in the declaration and in the definition.

Proposed Changes to Standard Wording

The wording in this paper is based on the current working paper (N3035) as amended by N3050.

15.4 Exception specifications [except.spec]

Modify the paragraphs in this section as follows.

3 If any declaration of a function has an exception-specification that is not a noexcept-specification allowing all exceptions, all declarations, including the definition and an explicit specialization, of that function shall have an compatible exception-specification with the same set of type-ids. If any declaration of a pointer to function, reference to function, or pointer to member function has an exception-specification, all occurrences of that declaration shall have an compatible exception-specification with the same set of type-ids. In an explicit instantiation an exception-specification may be specified, but is not required. If an exception-specification is specified in an explicit instantiation directive, it shall have the same set of type-ids asbe compatible with the exception-specifications of other declarations of that function. A diagnostic is required only if the sets of type-ids are differentexception-specifications are not compatible within a single translation unit.

[Insert a new paragraph before paragraph 5] Two exception-specifications are compatible if:

  • both are non-throwing (regardless of their form),
  • both have the form noexcept(constant-expression) and the constant-expressions are equivalent,
  • one exception-specification is a noexcept-specification allowing all exceptions and the other is of the form throw(type-id-list), or
  • both are dynamic-exception-specifications that have the same set of type-ids.

5 In such an assignment or initialization, exception-specifications on return types and parameter types shall match exactly be compatible. In other assignments or initializations, exception-specifications shall match exactly be compatible.

[Insert a new paragraph at the end of 15.4] [ Note: The use of dynamic-exception-specifications is deprecated (see annex D). - end note ]

D.5 Dynamic exception specifications [depr.except.spec]

1 The use of dynamic-exception-specifications is deprecated.

[Sutter02]A Pragmatic Look at Exception Specifications. http://www.gotw.ca/publications/mill22.htm
[Boost03]http://www.boost.org/development/requirements.html#Exception-specification
[N3050]
  1. Abrahams, R. Sharoni, and D. Gregor. Allowing Move Constructors to Throw. Document number N3050=10-0040, ISO C++ Committee Post-Pittsburgh Mailing, March, 2010.