ISO/ IEC JTC1/SC22/WG21 N1825

N1825=05-0085	Addressing Exception Specifications for Next Generation of C++
2005-06-27

Alisdair Meredith [alisdair.meredith@uk.renaultf1.com]

1. Motivation

Exception specifications in C98 are widely recognised as not very useful.  The generally accepted best practice is to not use them at all.  However they remain a part of the language, there are advocates of their use (with care) and we continue to resolve defect reports as they are come in.  Parts of the Standard Library are specified using them - at least as far as the no-throw guarantee.

For C++0x it would be very hard to remove a supported language feature, although we could relax the library usage.  We could deprecate the feature, although it is still not known what this would mean (Core DR 223)  Is there anything we can do to improve the situation without breaking existing code?

One approach suggests that only the no-throw specification is useful, and that we make sufficient changes for this guarantee to be useful to the translator.  That is not addressed in this paper.

A new direction is suggested by the similarity with the existing Exception Specification rules and the Contract Programming proposal (N1773)  In many ways an exception specification could be viewed as 5th kind of contract after pre/post-conditions and class/namespace invariants.  They would then gain usability from the parts of the contract proposal.  Coversely, contracts fall naturally into the grammar where Exception Specifications sit today.  Unifying the two would have the smallest impact on the grammar.


2. Observed Similarity between Exception Specifications and Contracts [n1773/n1800]

There are many similarities between Exception Specifications today, and the proposal for contracts on functions.

i/    They share a common purpose, "moving comments into code"
ii/   Exception specifications appear on the prototype of a function, but are not part of its signature.
iii/  All Checking is done at runtime
iv/   A failed check aborts the program
v/    While static checks are possible and will produce clearer error messages, they are (often) not required
vi/   The contract-violation mechanism is 'replacable'
vii/  The contract is inherited when overiding virtual functions


There are some differences in the contract proposal that would also enhance Exception Specifications
i/   An implementation defined means to turn off contract checks for 'release builds'

It is hoped that contracts will improve error diagnosis as implementation mature, and exception specification would benefit in a similar manner:

i/   Stronger static analysis of contracts to report possible violations at translation time
ii/  Runtime failures reported at the call point, rather than within the function violating its contract


Likewise, the contract proposal would do well to consider how exception specifications are handled with repsect to
i/   function pointers (15.4p2,4,5)
ii/  explicit template specializations (15.4p3) 
iii/ overiding virtual functions (15.4p4,14)

In particular, it is noted that even when runtime contract checks are disabled, the example in 15.4p14 would remain ill-formed.


Other advantages from integrating with the Contracts proposal include:
i/  smaller impact on the grammar if contracts fit into the grammar where exceptions specifacations sit today.


3. Proposal

Require an implementation defined means to turn off runtime contract checks, and so disable exception specification checks too.  [This is a part of the contracts proposal]  It is unspecified whether the different forms of contracts can be disabled separately (eg. only disable exception specifications)

Introduce a new exception base class, std::broken_contract, derived from std::exception.
Derive std::bad_exception from std::broken_contract.

Allow the proposed replacement functions for contract programming, of type std::broken_contract_handler, to exit by throwing exception derived from std::broken_contract.  This would only occur as a user customisation though, just as std::bad_exception is never thrown by the basic implementation.

Where possible, share wording with the contracts proposal where pre/post-conditions are performing similar checks to exception specifications.  The aim is to limit duplications/inconsistency in the text.


4. Implementing the proposal

The main change required for existing implementations is to introduce and document a mechanism to disable the runtime checks.  Everything else will work in the same way as today.  This addition should be relatively easy to add when doing other work to incorporate the contracts proposal.

The changes in the standard library are minor and should take no more than a day to implement, including test framework, given an otherwise conforming implementation.


5. Impact on Existing Standard

Precise wording changes are held back waiting on the precise wording of the Contracts proposal.  The expected impact is in the following areas.

Move whole of 15.4 into the clause on Contracts.
Move 15.5.1 into the library [18.6.3]
Move 15.5.2 in the Contracts clause
Move 15.5.3 into the library [18.6.4]
Rewrite 15.5 as a simple cross-reference to library functions that may be called in response to different failure modes.  Leave the description of the triggers in 18.6 to avoid duplications and inconsistencies.  (For instance, it is expected that contracts will introduce new means to trigger std::terminate)

Update 18.6.2: 
define class std::broken_contract, derived directly from std::exception.
std::bad_exception now derive from std::broken_contract
    No conforming program can assume there are no 'intermediate' classes between bad_exception and exception
change std::set_unexpected  to accept a function of type broken_contract_handler, rather than unexpected_handler (although they both typedef to the same thing, so existing user code will not be broken)
    
Note that 15.4 will not move word-for-word, and there is expected to be significant overlap with the wording on pre/post-condition contracts.  It is intended that duplication will be detected and removed during the wording of the contracts proposal.

Revise the grammar for 'direct-declarator':
Replace
    direct-declarator ( parameter-declaration-clause ) cv-qualifier-seq/opt/ exception-specification/opt/
    
With
    direct-declarator ( parameter-declaration-clause ) cv-qualifier-seq/opt/ contract-specification/opt/
    
contract-specification
    precondition/opt/ postcondition/opt/ exception-specification/opt/


Alternatively, contracts may be split into 3 sections:
i/ Make a new clause desribing functions, their decalarations and calling practices
This contains a section with contracts on functions: preconditions, postconditions and exception specification.  15.4 moves here

ii/ Add a section on class invariants to clause [9] - classes.
iii/ Add a section on namespace invariants to the namespace clause [7.3]

(ii) and (iii) would be untouched by this proposal.

6. Extended ideas

Several other ideas suggest themselves when looking into the broader picture of where 'exceptions as contracts' might go.  They are listed here for reference, although these larger changes introduce risk when trying to stabalise proposals for the next standard.

Throw-contracts could be extended to cover post-conditions in event on an exceptional function exit.
On exit, either go through post-condition or throw-contract depending on context - never both.
Alternatively, embed throw-contract *within* the post-condition.
	Allows a subset of postconditions to be checked on exceptional exit
	Current syntax is another way to write this for a function without post-conditions




Appendix A  Exception specifications in the Standard Library.

There are many empty throw specifications, these are omitted here for clarity.

3.7.3p2
void * operator new(std :: size_t ) throw (std :: bad_alloc );
void * operator new []( std :: size_t ) throw (std :: bad_alloc );
[note: std::bad_alloc will be incomplete in this case!!  Is that allowed?]

18.4p1
void * operator new(std :: size_t size ) throw (std :: bad_alloc );
void * operator new []( std :: size_t size ) throw (std :: bad_alloc );