Doc. no. | P0003R1 |
Date: | 2016-02-09 |
Project: | Programming Language C++ |
Audience: | Core Working Group |
Reply to: | Alisdair Meredith <ameredith1@bloomberg.net> |
Original version of the paper for the 2015 pre-Kona mailing.
Revised the wording to modify the proposed working draft after the Kona 2015 meeting, N4567. This accmodates renumbering a few clauses, notably Annex D where several other features have already been removed, and allowing for exception specifications in the type system, and the rewording of inheriting constructors.
Dynamic exception specifications were deprecated in C++11. This paper formally proposes removing the feature from C++17, while retaining the (still) deprecated throw() specification strictly as an alias for noexcept(true).
Exception specifications were added as part of the original design of the exception language feature. However, the experience of using them was less than desired, with the general consensus by the time the 1998 standard was published being, generally, to not use them. The standard library made the explicit choice to not use exception specifications apart from a handful of places where guaranteeing an empty (no-throwing) specification seemed useful. N741 gives a brief summary of the LWG thoughts at the time.
By the time C++11 was published, the feeling against exception specifications had grown, the limited real-world use generally reported negative user experience, and the language feature, renamed as dynamic exception specifcations, was deprecated, (N3051). A new languge feature, noexcept, was introduced to describe the important case of knowing when a function could guarantee to not throw any exceptions.
Looking ahead to C++17, there is a desire to incorporate exception specifications into the type system, N4533. This solves a number of awkward corners that arise from exception specifications not being part of the type of a function, but still does nothing for the case of deprecated dynamic exception specifications, so the actual language does not get simpler, and we must still document and teach the awkwards corners that arise from dynamic exception specifications outside the type system.
The recommendation of this paper is to remove dynamic exception specifications from the language. However, the syntax of the throw() specification should be retained, but no longer as a dyanmic exception specification. Its meaning should become strictly an alias for noexcept(true), and its usage should remain deprecated.
To minimize the impact on the current standard wording, the grammar term exception-specification is retained, although it has only one production and could be replaced entirely with noexcept-specification. Alternatively, the grammar term noexcept-specification could be retired.
The wording changes in this initial proposal are deliberately minimal in an effort to ensure the least risk of accidental change. However, rather than using the language of sets of compatible exception specifications (where there are now only two such sets, the empty set and the set of all types) it would be possible to write a simpler, clearer form describing instead functions that permit exceptions, and functions that permit no exceptions. While such a specification would be preferred, it is also beyond the drafting skills of the proposal author.
The redrafting goes slightly beyond minimal by eliminating use of the pseudo-type "any". This change, while improving clarity, also avoids confusion with the standard library type any from the Library Fundamentals TS in the event that it becomes part of a future standard library.
Dynamic exception specifications are a failed experiment, but this is not immediately clear to novices, especially where the "novice" is an experienced developer coming from other languages such as Java, where exception specifications may be more widely used. Their continuing presence, especially in an important part of the languge model (exceptions are key to understanding constructors, destructors, and RAII) is an impediment that must be explained. It remains embarassing to explain to new developers that there are features of the language that conforming compilers are required to support, yet should never be used.
Exception specifications in general occupy an awkward corner of the grammer, where they do not affect the type system, yet critically affect how a virtual function can be overridden, or which functions can bind to certain function pointer variables. As noted above, N4533 would go a long way to resolving that problem for noexcept exception specifications, which makes the hole left for dynamic exception specifications even more awkward and unusual for the next generation of C++ developers.
C++17 is on schedule to be a release with several breaking changes for old code, with the standard library removing auto_ptr, the classic C++98 fuction binders, and more. Similarly, it is expected that the core language will lose trigraphs, the register keyword, and the increment operator for bool. This would be a good time to make a clean break with long discouraged (and actively deprecated) parts of the language.
The proposed change would resolve core issue 596 as no longer relevant (NAD), and should simplify core issue 1351, although that is marked as a Defect Report that was not yet applied to the working paper the proposed wording below was drafted from.
There is certainly some body of existing code that has not heeded the existing best practice of discouraging dynamic exception specifications, and has not yet accounted for the feature being deprecated in C++11. It is not clear how much of such code would be expected to port unmodified into a C++17 (or beyond) world, and the change is relatively simple - just strike the (non-empty) dynamic exception specification to retain equivalent meaning. The key difference is that the unexpected handler will not now be called to translate unexpected exceptions. In rare cases, this would allow a new exception type to propage, rather than calling terminate to abort. If enforcing that semantic is seen as important in production systems, there is a more intrusive workaround available:
void legacy()throw(something)try { // function body as before } catch(const something&) { throw; } catch(...) { terminate(); }
It is thought that the empty dynamic exception specification, throw(), was much more widely used in practice, often in the mistaken impression that compilers would use this information to optimize code generation where no exception could propagate, where in fact this is strictly a pessimization that forces stack unwinding to the function exit, and then calling the unexcepted callback in a manner that is guatanteed to fail before the subsequent call to terminate. This paper proposes treating such (still deprecate) exception specifications as synonyms for noexcept(true), yielding the performance benefit many were originally expecting.
It should also be noted that at least one widely distributed compiler has still not implemented this feature in 2015, and at least one vendor has expressed a desire to never implement the deprecated feature (while that vendor has implemented the noexcept form of exception specification). Code on that platform would not be adversely impacted by the proposed removal, and portable code must always have allowed for the idiosynracies of this platform.
One remaining task is to survey popular open source libraries and see what level of usage, if any, remains in large, easily accessible codebases.
The first version of this paper was presented to the Evolution Working Group at the Kona 2015 meeting, and was recommended to advance to Core 'as written'. Specifically, that means retaining the empty exception specification as an alternate spelling of noexcept(true):
SF | WF | N | WA | SA |
12 | 13 | 6 | 0 | 0 |
By choosing to keep the throw() specification, we look to retain source compatibility with a large body of existing code that has used this specification to indicate code that should not throw. However, we also change the meaning of that specification to be exactly the same as a plain noexcept specification, which means that an implementation is no longer permitted to call unexpected when a violation is detected (which was required in all previous standards) and the previously mnadated stack unwinding might no longer occur. This means that a conforming C++17 implementation is not permitted to simply retain the C++14 semantics while issuing an "extension" diagnostic.
As the author is not a regular drafter of Core wording, a quick rationale for some of the most obvious wording decisions seems helpful, especially when drawing attention to changes that were deliberately not made.
Where there is some question over these decisions, it would probably be more efficient to have direct feedback here, than sowing repeating detailed comments throughout the wording - at least for the first round of review.
To minimize the impact on the existing grammar and wording, the term exception-specification is retained at exactly the same place in the grammar. However, it loses its ability to describe anything other than a noexcept-specification. The empty throw specification becomes a (deprecated) synonym for noexcept(true), and the noexcept-specification production is retired.
An alternative formuation might have been to retire the original exception-specification itself, forcing us to find and address each existing use in the text - either to simply replace it with noexcept-specification, or rewrite that part of the document to no longer refer to exception specifications at all.
The author has gone with the first option as it will be a less distruptive set of changes, and the Core working group has the expertise to spot and remove any remaining redundancies as a later clean-up (if desired).
The current text regarding exception specifications is written in terms of sets of types that might pass through the specification. This drafting removes the notion of an "any" type, and simplifies the world down to two sets: the empty set, and the set of all types.
The wording could be simplified and made clear by simply talking about specifications that permit exceptions to pass, and those that do not, and simply skip the introduction of a simple set algebra. However, that would be a much larger rewrite that should not be undertaken by a mere amateur in the world of Core drafting, so is left as an exercise for the Core Working Group itself, should it feel strongly motivated. It is also the kind of clean-up that can occur at a more deliberate pace after the initial propsal is adopted.
The simple answer is that would be a technical change that needs to be separately blessed by EWG. The wording demanding unwinding at a violated exception specifcation can be cleanly removed with the whole of sub-clause 15.5.2. However, the implementation-defined choice on whether to unwind through an exception-specification should remain, to support the variety of exception unwinders deployed in production compilers today.
There are a few clauses that talk about types when used in exception specifications, such as describing the name look-up rules, or requirements for type-completeness. With the removal of dynamic-exception-specifications, the first attempt at drafting updated these clauses too (and eagle-eyed readers may notice comments in the HTML source with that discarded drafting). However, in most or all cases, the existing text remains valid, but more specialized. There is still the use case of an exception specification using a noexcept operator, which would still rely on this wording for expressions within that operator.
For refernence (and review by the diligent) the list of clauses affected by this decision to make no changes is:
Sometimes the wording refers to an exception specification, and at other times it refers to an exception-specification. This is generally intentional, where the former refers to the notion of an exception specification, which includes implicit exception specifications, where the latter refers to explicit use of the grammar production.
Oops - remember to include throw() specifications might no longer unwind, but were required to unwind in C++14.
It should be possible to greatly simplify this paragraph, if not eliminate it entirely.
Two attempts are made at drafting new wording.
The first stays as close as possible to the original wording, retaining the idea of sets of types that may pass through an exception specification, but simplifiying the idea so that only two sets exist in practice: the empty set, and the set of all types. The intent is to provide a basis for stable wording that can be adopted with confidence, at the earliest opportunity.
The second attempts to simplify the notion of exception specifications to those that allow exceptions, and those that do not allow exceptions. This removes the notion of set algebra from the standard, and talks much more directly to the topic of propagating exceptions. It is more of a rewrite though, and might be considered riskier as a consequence, even if it would be the preferred long term approach.
1 A prvalue of type “pointer to noexcept function” can be converted to a prvalue of type “pointer to function”. The result is a pointer to the function. A prvalue of type “pointer to member of type noexcept function” can be converted to a prvalue of type “pointer to member of type function”. The result points to the member function. [ Example:
— end example ]void (*p)()throw(int); void (**pp)() noexcept = &p; // error: cannot convert to pointer to noexcept function struct S { typedef void (*p)(); operator p(); }; void (*q)() noexcept = S(); // error: cannot convert to pointer to noexcept function
8 The return type, the parameter-type-list, the ref-qualifier,
the cv-qualifier-seq, and whether the function has a non-throwing
exception-specification, but not the default arguments (8.3.6) or
the exception specification (15.4), are part of the function type.
[Note: Function types are checked during the assignments and
initializations of pointers to functions, references to functions, and pointers
to member functions. — end note ]
4 [ Example:
struct S { constexpr S() = default; // ill-formed: implicit S() is not constexpr S(int a = 0) = default; // ill-formed: default argument void operator=(const S&) = default; // ill-formed: non-matching return type ~S() noexcept(false)throw(int)= default; // deleted: exception specification does not match private: int i; S(S&); // OK: private copy constructor }; S::S(S&) = default; // OK: defines copy constructor
— end example ]
(4.7) — In a dynamic-exception-specification (15.4); the pattern is a type-id.
7 A handler is considered active when initialization is complete for the
parameter (if any) of the catch clause. [ Note: The stack will have been
unwound at that point. — end note ] Also, an implicit handler is
considered active when std::terminate() or
std::unexpected() is entered due to a throw. A handler is no
longer considered active when the catch clause exits or when
std::unexpected() exits after being entered due to a throw.
1 The exception specification of a function is a (possibly empty) set of
types, indicating that the function might exit via an exception that
matches a handler of one of the types in the set; the (conceptual) set of
all types is used to denote that the function might exit via an exception of
arbitrary type. If the set is empty, the function is said to have a
non-throwing exception specification. The exception specification is
either defined explicitly by using an exception-specification as a
suffix of a function declaration's declarator (8.3.5) or implicitly.
exception-specification:dynamic-exception-specificationnoexcept-specificationnoexcept ( constant-expression ) noexcept throw ( )dynamic-exception-specification:throw ( type-id-listopt )type-id-list:type-id ...opttype-id-list , type-id ...optnoexcept-specification:noexcept ( constant-expression )noexcept
In an exceptionnoexcept-specification,
the constant-expression, if supplied, shall be a constant expression
(5.20) that is contextually converted to bool (Clause 4). A
( token that follows noexcept is part of the
exceptionnoexcept-specification and does not
commence an initializer (8.5). The exception-specification
throw() is deprecated (see Annex D), and equivalent to the
exception-specification noexcept.
2 A type denoted in a dynamic-exception-specification shall not denote
an incomplete type or an rvalue reference type. A type denoted in a
dynamic-exception-specification shall not denote a pointer or reference
to an incomplete type, other than “pointer to cv void”. A type
cv T denoted in a dynamic-exception-specification is
adjusted to type T. A type “array of T”, or function type
T denoted in a dynamic-exception-specification is adjusted to
type “pointer to T”. A dynamic-exception-specification denotes
an exception specification that is the set of adjusted types specified thereby.
3 The exception-specification noexcept or noexcept(constant-expression), where the constant-expression yields true, denotes an exception specification that is the empty set. The exception-specification noexcept(constant-expression), where the constant-expression yields false, or the absence of an exception-specification in a function declarator other than that for a destructor (12.4) or a deallocation function (3.7.4.2) denotes an exception specification that is the set of all types.
4 Two exception-specifications are compatible if the sets of types they denote are the same.
5 If any declaration of a function has an exception-specification
that is not a noexcept-specification allowing
noall exceptions, all declarations, including the
definition and any explicit specialization, of that function shall have a
compatible exception-specification. 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
a compatible exception-specification. If a declaration of a function
has an implicit exception specification, other declarations of the function
shall not specify an exception-specification. 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 be compatible with the
exception-specifications of other declarations of that function. A
diagnostic is required only if the exception-specifications are not
compatible within a single translation unit.
6 If a virtual function has an exception specification, all declarations, including the definition, of any function that overrides that virtual function in any derived class shall only allow exceptions that are allowed by the exception specification of the base class virtual function, unless the overriding function is defined as deleted. [ Example:
struct B { virtual void f() noexceptThe declaration of D::f is ill-formed because it allows all exceptions, whereas B::f allows no exceptionsthrow (int, double); virtual void g(); }; struct D: B { void f(); // ill-formed void g() noexeptthrow (int); // OK };
7 An exception-specification can include the same type more than once
and can include classes that are related by inheritance, even though doing so
is redundant. [ Note: An exception-specification can also include
the class std::bad_exception (18.8.3). — end note ]
8 A function is said to allow an exception of type E if its
exception specification contains a type T for which a handler of type
T would be a match (15.3) for an exception of type E.
A function is said to allow all exceptions if its exception specification is
the set of all types. Otherwise a function does not allow any
exceptions.
9 Whenever an exception of type E is thrown and the search
for a handler (15.3) encounters the outermost block of a function with an
exception specification that does not allow any
exceptionsE, then, the function
std::terminate() is called (15.5.1).
(9.1) — if the function definition has a
dynamic-exception-specification, the function std::unexpected()
is called (15.5.2),
(9.2) — otherwise, the function std::terminate() is called (15.5.1).
[ Example:
class X { };class Y { };class Z: public X { };class W { };void f() throw (X, Y) {int n = 0;if (n) throw X(); // OKif (n) throw Z(); // also OKthrow W(); // will call std::unexpected()}
— end example ]
[Note: A function can have multiple declarations with different
non-throwing exception-specifications; for this purpose, the one on the
function definition is used. — end note ]
10 An implementation shall not reject an expression merely because when executed it throws or might throw an exception that the containing function does not allow. [ Example:
extern void f()the call to f is well-formed even though when called, f might throw an exceptionthrow(X, Y)noexcept; void g()throw(X){ f(); // OK }
11 [ Note: An exception specification is not considered part of a
function’s type; see 8.3.5. — end note ]
12 AThe set of potential exceptions of
a given context is either a typea set of types that might
be thrown as an exception or a pseudo-type, denoted by "any",
that represents the situation where an exception of an arbitrary type might be
thrown; the (conceptual) set of all types is used to denote that an
exception of arbitrary type might be thrown. A subexpression e1
of an expression e is an immediate subexpression if there is no
subexpression e2 of e such that e1 is a
subexpression of e2.
13 The set of potential exceptions of a function, function pointer, or
member function pointer f is defined as follows:
(13.1) — If the exception specification of f is the set of all
types, the set consists of the pseudo-type "any".
(13.2) — Otherwise, the set consists of every type in the exception
specification of f.
14 The set of potential exceptions of an expression e is empty if e is a core constant expression (5.20). Otherwise, it is the union of the sets of potential exceptions of the immediate subexpressions of e, including default argument expressions used in a function call, combined with a set S defined by the form of e, as follows:
(14.1) — If e is a function call (5.2.2):
(14.1.1) — If its postfix-expression is a (possibly parenthesized) id-expression (5.1.1), class member access (5.2.5), or pointer-to-member operation (5.5) whose cast-expression is an id-expression, S is the set of potential exceptions of the entity selected by the contained id-expression (after overload resolution, if applicable).
(14.1.2) — Otherwise, if the postfix-expression has type "noexcept function" or "pointer to noexcept function", S is the empty set.
(14.1.3) — Otherwise, S is the set of all
typescontains the pseudo-type "any".
(14.2) — If e implicitly invokes a function (such as an overloaded operator, an allocation function in a new-expression, or a destructor if e is a full-expression (1.9)), S is the set of potential exceptions of the function.
(14.3) — If e initializes an object of type D using an inherited constructor for a class of type B (12.6.3), S also contains the sets of potential exceptions of the implied constructor invocations for subobjects of D that are not subobjects of B (including default argument expressions used in such invocations) as selected by overload resolution, and the sets of potential exceptions of the initialization of non-static data members from brace-or-equal-initializers (12.6.2).
(14.4) — if e is a throw-expression (5.17), S
is the set of all typesconsists of the type of the exception
object that would be initialized by the operand, if present, or the pseudo-type
"any" otherwise.
(14.5) — if e is a dynamic_cast expression that casts
to a reference type and requires a run-time check (5.2.7), S is
the set of all typesconsists of the type
std::bad_cast.
(14.6) — if e is a typeid expression applied to a glvalue
expression whose type is a polymorphic class type (5.2.8), S is
the set of all typesconsists of the type
std::bad_typeid.
(14.7) — if e is a new-expression with a non-constant
expression in the noptr-new-declarator (5.3.4), S
is the set of all typesconsists of the type
std::bad_array_new_length.
[ Example: Given the following declarations
void f()the set of potential exceptions for some sample expressions is:throw(int)noexcept(false); void g(); struct A { A(); }; struct B { B() noexcept; }; struct D { D()throw (double)noexcept(false); };
(14.87.1) — for f(), the set is the
set of all types consists of int;
(14.97.2) — for g(), the set is the
set of all types consists of "any";
(14.107.3) — for new A, the set is
the set of all types consists of "any";
(14.117.4) — for B(), the set is empty;
(14.127.5) — for new D, the set is
the set of all types consists of "any" and
double.
— end example ]
15 A function with an implied non-throwing exception specification, where the function’s type is declared to be T, is instead considered to be of type "noexcept T".
16 Given anAn implicitly declared special member function
f of some class X, the set of potential exceptions
of the implicitly-declared special member function f is
considered to have an implicit exception specification that consists of
all the members from the following sets:
(16.1) — if f is a constructor,
(16.1.1) — the sets of potential exceptions of the constructor invocations
(16.1.1.1) — for X's non-variant non-static data members,
(16.1.1.2) — for X's direct base classes, and
(16.1.1.3) — if X is non-abstract (10.4), for X's virtual base classes,
(including default argument expressions used in such invocations) as selected by overload resolution for the implicit definition of f (12.1). [ Note: Even though destructors for fully-constructed subobjects are invoked when an exception is thrown during the execution of a constructor (15.2), their exception specifications do not contribute to the exception specification of the constructor, because an exception thrown from such a destructor could never escape the constructor (15.1, 15.5.1). — end note]
(16.1.2) — the sets of potential exceptions of the initialization of non-static data members from brace-or-equal-initializers that are not ignored (12.6.2);
(16.2) — if f is an assignment operator, the sets of potential exceptions of the assignment operator invocations for X's non-variant non-static data members and for X's direct base classes (including default argument expressions used in such invocations), as selected by overload resolution for the implicit definition of f (12.8);
(16.3) — if f is a destructor, the sets of potential exceptions of the destructor invocations for X's non-variant non-static data members and for X's virtual and direct base classes.
17 An implicitly-declared special member function (Clause 12) is considered to
have an implicit exception specification, as follows, where S is the
set of potential exceptions of the implicitly-declared member function:
(17.1) — if S contains the pseudo-type "any", the
implicit exception specification is the set of all types;
(17.2) — otherwise, the implicit exception specification contains all the
types in S.
17[ Note: An instantiation of an inheriting constructor template has an implied exception specification as if it were a non-template inheriting constructor. — end note ]
[ Example:struct A { A(int = (A(5), 0)) noexcept; A(const A&) throw(); A(A&&) throw(); ~A()throw(X); }; struct B { B() throw(); B(const B&) = default; // exception specification contains no types B(B&&, int = (throw Y(), 0)) noexcept; ~B() noexcept(false)throw(Y); }; int n = 7; struct D : public A, public B { int * p = new (std::nothrow) int[n]; // exception specification of D::D() contains the set of all typesX and std::bad_array_new_length// exception specification of D::D(const D&) contains no types // exception specification of D::D(D&&) contains the set of all typesY// exception specification of D::~D() contains the set of all typesX and Y};
Furthermore, if A::~A() or B::~B() were virtual,
the exception specification of D::~D() would not be as
restrictive as that of A::~A, and the program would be ill-formed
since a function that overrides a virtual function from a base class shall have
an exception-specification at least as restrictive as that in the base
class. — end example ]
18 A deallocation function (3.7.4.2) with no explicit exception-specification has an exception specification that is the empty set.
19 An exception-specification is considered to be needed when:
(19.1) — in an expression, the function is the unique lookup result or the selected member of a set of overloaded functions (3.4, 13.3, 13.4);
(19.2) — the function is odr-used (3.2) or, if it appears in an unevaluated operand, would be odr-used if the expression were potentially-evaluated;
(19.3) — the exception-specification is compared to that of another declaration (e.g., an explicit specialization or an overriding virtual function);
(19.4) — the function is defined; or
(19.5) — the exception-specification is needed for a defaulted special member function that calls the function. [ Note: A defaulted declaration does not require the exception-specification of a base member function to be evaluated until the implicit exception-specification of the derived function is needed, but an explicit exception-specification needs the implicit exception-specification to compare against. — end note ]
The exception-specification of a defaulted special member function is evaluated as described above only when needed; similarly, the exception-specification of a specialization of a function template or member function of a class template is instantiated only when needed.
20 In a dynamic-exception-specification, a type-id followed by an
ellipsis is a pack expansion (14.5.3).
21 [ Note: The use of dynamic-exception-specifications is
deprecated (see Annex D). — end note ]
1 The functions std::terminate() (15.5.1) and
std::unexpected() (15.5.2) areis used by the
exception handling mechanism for coping with errors related to the exception
handling mechanism itself. The function std::current_exception()
(18.8.5) and the class std::nested_exception (18.8.6) can be used by a
program to capture the currently handled exception.
1 In some situations exception handling must be abandoned for less subtle error handling techniques. [ Note: These situations are:
(1.1) — when the exception handling mechanism, after completing the initialization of the exception object but before activation of a handler for the exception (15.1), calls a function that exits via an exception, or
(1.2) — when the exception handling mechanism cannot find a handler for a thrown exception (15.3), or
(1.3) — when the search for a handler (15.3) encounters the outermost
block of a function with an
exceptionnoexcept-specification that does not
allow the exceptions (15.4), or
(1.4) — when the destruction of an object during stack unwinding (15.2) terminates by throwing an exception, or
(1.5) — when initialization of a non-local variable with static or thread storage duration (3.6.3) exits via an exception, or
(1.6) — when destruction of an object with static or thread storage duration exits via an exception (3.6.4), or
(1.7) — when execution of a function registered with std::atexit or std::at_quick_exit exits via an exception (18.5), or
(1.8) — when a throw-expression (5.17) with no operand attempts to rethrow an exception and no exception is being handled (15.1), or
(1.9) — when std::unexpected exits via an exception of a type
that is not allowed by the previously violated exception specification, and
std::bad_exception is not included in that exception specification
(15.5.2), or
(1.10) — when the implementation’s default unexpected exception handler
is called (D.5.1), or
(1.11) — when the function std::nested_exception::rethrow_nested is called for an object that has captured no exception (18.8.7), or
(1.12) — when execution of the initial function of a thread exits via an exception (30.3.1.2), or
(1.13) — when the destructor or the copy assignment operator is invoked on an object of type std::thread that refers to a joinable thread (30.3.1.3, 30.3.1.4), or
(1.14) — when a call to a wait(), wait_until(), or wait_for() function on a condition variable (30.5.1, 30.5.2) fails to meet a postcondition.
— end note ]
2 In such cases, std::terminate() is called (18.8.4). In the situation
where no matching handler is found, it is implementation-defined whether or not
the stack is unwound before std::terminate() is called. In the
situation where the search for a handler (15.3) encounters the outermost block
of a function with an
exceptionnoexcept-specification that does not
allow the exceptions (15.4), it is implementation-defined
whether the stack is unwound, unwound partially, or not unwound at all before
std::terminate() is called. In all other situations, the stack shall
not be unwound before std::terminate() is called. An implementation
is not permitted to finish stack unwinding prematurely based on a determination
that the unwind process will eventually cause a call to
std::terminate().
1 If a function with a dynamic-exception-specification exits via an
exception of a type that is not allowed by its exception specification, the
function std::unexpected() is called (D.5) immediately after
completing the stack unwinding for the former function.
2 [ Note: By default, std::unexpected() calls
std::terminate(), but a program can install its own handler function
(D.5.2). In either case, the constraints in the following paragraph apply.
— end note ]
3 The std::unexpected() function shall not return, but it can throw
(or rethrow) an exception. If it throws a new exception which is allowed by
the exception specification which previously was violated, then the search for
another handler will continue at the call of the function whose exception
specification was violated. If it exits via an exception of a type that the
dynamic-exception-specification does not allow, then the following
happens: If the dynamic-exception-specification does not include the
class std::bad_exception (18.8.2) then the function
std::terminate() is called, otherwise the thrown exception is replaced
by an implementation-defined object of type std::bad_exception and the
search for another handler will continue at the call of the function whose
dynamic-exception-specification was violated.
4 [Note: Thus, a dynamic-exception-specification guarantees that
a function exits only via an exception of one of the listed types. If the
dynamic-exception-specification includes the type
std::bad_exception then any exception type not on the list may be
replaced by std::bad_exception within the function
std::unexpected(). — end note]
1 The C++ standard library provides default versions of the following handler
functions (Clause 18):
(1.1) — unexpected_handler
(1.2) — terminate_handler
2 A C++ program may install different handler functions during execution, by supplying a pointer to a function defined in the program or the library as an argument to (respectively):
(2.1) — set_new_handler
(2.2) — set_unexpected
(2.3) — set_terminate
See also: subclauses 18.6.3, Storage allocation errors, and 18.8, Exception handling.
3 A C++ program can get a pointer to the current handler function by calling the following functions:
(2.1) — get_new_handler
(2.2) — get_unexpected
(2.3) — get_terminate
4 Calling the set_* and get_* functions shall not incur a data race. A call to any of the set_* functions shall synchronize with subsequent calls to the same set_* function and to the corresponding get_* function.
(2.2) — for handler functions (18.6.3.3, 18.8.4.1, D.5.1), if the
installed handler function does not implement the semantics of the applicable
Required behavior: paragraph.
1 Any of the functions defined in the C++ standard library can report a failure
by throwing an exception of a type described in its Throws: paragraph.
An implementation may strengthen the exception specification for a
non-virtual function by adding a non-throwing
noexcept-specification.
2 A function may throw an object of a type not listed in its Throws clause if its type is derived from a type named in the Throws clause and would be caught by an exception handler for the base type.
3 Functions from the C standard library shall not throw exceptions188 except when such a function calls a program-supplied function that throws an exception.189
4 Destructor operations defined in the C++ standard library shall not throw
exceptions. Every destructor in the C++ standard library shall behave as if it
had a non-throwing exception specification. Any other functions defined in the
C++ standard library that do not have an exception-specification may
throw implementation-defined exceptions unless otherwise
specified.190 An implementation may strengthen this implicit
exception-specification by adding an explicit
one.191
5 An implementation may strengthen the exception specification for a non-virtual function by adding an exception-specification that does not allow exceptions.
191) That is, an implementation may provide an explicit
exception-specification that defines the subset of "any"
exceptions thrown by that function. This implies that the implementation may
list implementation-defined types in such an exception-specification.
1 The header <exception> defines several types and functions related to the handling of exceptions in a C++ program.
namespace std { class exception; class bad_exception; class nested_exception;typedef void (*unexpected_handler)();unexpected_handler get_unexpected() noexcept;unexpected_handler set_unexpected(unexpected_handler f) noexcept;[[noreturn]] void unexpected();typedef void (*terminate_handler)(); terminate_handler get_terminate() noexcept; terminate_handler set_terminate(terminate_handler f) noexcept; [[noreturn]] void terminate() noexcept; int uncaught_exceptions() noexcept; // D.9X, uncaught_exception (deprecated) bool uncaught_exception() noexcept; typedef unspecified exception_ptr; exception_ptr current_exception() noexcept; [[noreturn]] void rethrow_exception(exception_ptr p); template <class E> exception_ptr make_exception_ptr(E e) noexcept; template <class T> [[noreturn]] void throw_with_nested(T&& t); template <class E> void rethrow_if_nested(const E& e); }
1 The class bad_exception defines the type of objects
referenced by the exception_ptr returned from a call to
current_exception (18.8.6 [propagation]) when the currently active
exception object fails to copythrown as described in (15.5.2).
4 Member function swap() shall have an
exception-specification that does not allow exceptions a
noexcept-specification which is equivalent to
noexcept(true).
(2.40) — Throw specifications on a single function declaration [256].
15.4
Change: Remove dynamic exception specifications.
Rationale: Dynamic exception specifications were a deprecated feature that were complex and brittle in use. They interacted badly with the type system, which would be a more significant issue in C++17 where exception specifications become part of the function type
Effect on original feature: A valid C++ 2014 function declaration, member-function-declaration, function-pointer-declaration, or function-reference definition, which has a potentially throwing dyanmic exception specification will be rejected as ill-formed in this international standard. Violating a non-throwing dynamic exception specification will call terminate rather than unexcepted and might not perform stack unwinding prior to such a call.
1 The exception-specification throw()use of
dynamic-exception-specifications is deprecated.
typedef void (*unexpected_handler)();
1 The type of a handler function to be called by unexpected()
when a function attempts to throw an exception not listed in its
dynamic-exception-specification.
2 Required behavior: An unexpected_handler shall not return.
See also 15.5.2.
3 Default behavior: The implementation's default
unexpected_handler calls std::terminate().
unexpected_handler set_unexpected(unexpected_handler f) noexcept;
1 Effects: Establishes the function designated by f as the
current unexpected_handler.
2 Remark: It is unspecified whether a null pointer value designates the default
unexpected_handler.
3 Returns: The previous unexpected_handler.
unexpected_handler get_unexpected() noexcept;
1 Returns: The current unexpected_handler. [ Note: This
may be a null pointer value. — end note ]
[[noreturn]] void unexpected();
1 Remarks: Called by the implementation when a function exits via an
exception not allowed by its exception-specification (15.5.2), in effect
after evaluating the throw-expression (D.5.1). May also be called
directly by the program.
2 Effects: Calls the current unexpected_handler function. [
Note: A default unexpected_handler is always considered a
callable handler in this context. — end note ]
In addition to the previously proposed edits, make the following changes, and completely replace clause 15.4 [except.spec] with the text below.
Drafting notes:
1 The noexcept operator determines whether the evaluation of its operand, which is an unevaluated operand (Clause 5), can throw an exception (15.1).
noexcept-expression:noexcept ( expression )
2 The result of the noexcept operator is a constant of type bool and is a prvalue.
3 The result of the noexcept operator is true unless the
expression allows exceptions (15.4) if the set of potential
exceptions of the expression (15.4) is empty, and false
otherwise.
3 If a function that is explicitly defaulted is declared with an
exception-specification that allows exceptions that are not allowed
byis not compatible (15.4) with the exception specification of
the implicit declaration, then
(3.1) — if the function is explicitly defaulted on its first declaration, it is defined as deleted;
(3.2) — otherwise, the program is ill-formed.
1 The exception specification of a function is a (possibly empty)
set of types, indicating that the function might exit via an exception
that matches a handler of one of the types in the set; the
(conceptual) set of all types is used to denote that the function might exit
via an exception of arbitrary type. If the set is empty, the function is said
to have a non-throwing exception specification. The exception
specification is either defined explicitly by using an
exception-specification as a suffix of a function declaration's
declarator (8.3.5) or implicitly.
exception-specification:dynamic-exception-specificationnoexcept-specificationdynamic-exception-specification:throw ( type-id-listopt )type-id-list:type-id ...opttype-id-list , type-id ...optnoexcept-specification:noexcept ( constant-expression )noexcept
In a noexcept-specification, the constant-expression, if
supplied, shall be a constant expression (5.20) that is contextually converted
to bool (Clause 4). A ( token that follows noexcept
is part of the noexcept-specification and does not commence an
initializer (8.5).
2 A type denoted in a dynamic-exception-specification shall not denote
an incomplete type or an rvalue reference type. A type denoted in a
dynamic-exception-specification shall not denote a pointer or reference
to an incomplete type, other than “pointer to cv void”. A type
cv T denoted in a dynamic-exception-specification is
adjusted to type T. A type “array of T”, or function type
T denoted in a dynamic-exception-specification is adjusted to
type “pointer to T”. A dynamic-exception-specification denotes
an exception specification that is the set of adjusted types specified thereby.
3 The exception-specification noexcept or
noexcept(constant-expression), where the
constant-expression yields true, denotes an exception
specification that is the empty set. The exception-specification
noexcept(constant-expression), where the
constant-expression yields false, or the absence of an
exception-specification in a function declarator other than that for a
destructor (12.4) or a deallocation function (3.7.4.2) denotes an exception
specification that is the set of all types.
4 Two exception-specifications are compatible if the sets of
types they denote are the same.
5 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 any explicit specialization, of that
function shall have a compatible exception-specification. 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 a compatible exception-specification. If a
declaration of a function has an implicit exception specification, other
declarations of the function shall not specify an
exception-specification. 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 be compatible with the exception-specifications of
other declarations of that function. A diagnostic is required only if the
exception-specifications are not compatible within a single translation
unit.
6 If a virtual function has an exception specification, all declarations,
including the definition, of any function
that overrides that virtual function in any derived class shall
only allow exceptions that are allowed by the
exception specification of the base class virtual function, unless the
overriding function is defined as deleted. [ Example:
struct B {virtual void f() throw (int, double);virtual void g();};struct D: B {void f(); // ill-formedvoid g() throw (int); // OK};
The declaration of D::f is ill-formed because it allows all
exceptions, whereas B::f allows only int and double.
— end example]
7 An exception-specification can include the same type more than once
and can include classes that are related by inheritance, even though doing so
is redundant. [ Note: An exception-specification can also include
the class std::bad_exception (18.8.3). — end note ]
8 A function is said to allow an exception of type E if its
exception specification contains a type T for which a handler of type
T would be a match (15.3) for an exception of type E.
A function is said to allow all exceptions if its exception specification is
the set of all types.
9 Whenever an exception of type E is thrown and the search
for a handler (15.3) encounters the outermost block of a function with an
exception specification that does not allow E, then
(9.1) — if the function definition has a
dynamic-exception-specification, the function std::unexpected()
is called (15.5.2),
(9.2) — otherwise, the function std::terminate() is called (15.5.1).
[ Example:
class X { };class Y { };class Z: public X { };class W { };void f() throw (X, Y) {int n = 0;if (n) throw X(); // OKif (n) throw Z(); // also OKthrow W(); // will call std::unexpected()}
— end example ]
[Note: A function can have multiple declarations with different
non-throwing exception-specifications; for this purpose, the one on the
function definition is used. — end note ]
10 An implementation shall not reject an expression merely because when
executed it throws or might throw an exception that the containing function
does not allow. [ Example:
extern void f() throw(X, Y);void g() throw(X) {f(); // OK}
the call to f is well-formed even though when called, f might
throw exception Y that g does not allow. — end
example ]
11 [ Note: An exception specification is not considered part of a
function’s type; see 8.3.5. — end note ]
12 A potential exception of a given context is either a type
that might be thrown as an exception or a pseudo-type, denoted by
"any", that represents the situation where an exception of an
arbitrary type might be thrown. A subexpression e1 of an
expression e is an immediate subexpression if there is no
subexpression e2 of e such that e1 is a
subexpression of e2.
13 The set of potential exceptions of a function, function pointer, or
member function pointer f is defined as follows:
(13.1) — If the exception specification of f is the set of all
types, the set consists of the pseudo-type "any".
(13.2) — Otherwise, the set consists of every type in the exception
specification of f.
14 The set of potential exceptions of an expression e is empty
if e is a core constant expression (5.20). Otherwise, it is the union
of the sets of potential exceptions of the immediate subexpressions of
e, including default argument expressions used in a function call,
combined with a set S defined by the form of e, as follows:
(14.1) — If e is a function call (5.2.2):
(14.1.2) — Otherwise, if the postfix-expression has type
"noexcept function" or "pointer to noexcept
function", S is the empty set.
(14.1.3) — Otherwise, S contains the pseudo-type "any".
(14.2) — If e implicitly invokes a function (such as an
overloaded operator, an allocation function in a new-expression, or a
destructor if e is a full-expression (1.9)), S is the set of
potential exceptions of the function.
(14.3) — If e initializes an object of type D using an
inherited constructor for a class of type B (12.6.3), S also
contains the sets of potential exceptions of the implied constructor
invocations for subobjects of D that are not subobjects of B
(including default argument expressions used in such invocations) as selected
by overload resolution, and the sets of potential exceptions of the
initialization of non-static data members from
brace-or-equal-initializers (12.6.2).
(14.4) — if e is a throw-expression (5.17), S
consists of the type of the exception object that would be initialized by the
operand, if present, or the pseudo-type "any" otherwise.
(14.5) — if e is a dynamic_cast expression that casts
to a reference type and requires a run-time check (5.2.7), S consists
of the type std::bad_cast.
(14.6) — if e is a typeid expression applied to a glvalue
expression whose type is a polymorphic class type (5.2.8), S consists
of the type std::bad_typeid.
(14.7) — if e is a new-expression with a non-constant
expression in the noptr-new-declarator (5.3.4), S
consists of the type std::bad_array_new_length.
[ Example: Given the following declarations
void f() throw(int);void g();struct A { A(); };struct B { B() noexcept; };struct D { D() throw (double); };
the set of potential exceptions for some sample expressions is:
(14.8) — for f(), the set consists of int;
(14.9) — for g(), the set consists of "any";
(14.10) — for new A, the set consists of "any";
(14.11) — for B(), the set is empty;
(14.12) — for new D, the set consists of "any" and
double.
— end example ]
15 A function with an implied non-throwing exception specification, where the
function’s type is declared to be T, is instead considered to be of
type "noexcept T".
16 Given an implicitly declared special member function f of some
class X, the set of potential exceptions of the implicitly-declared
special member function f consists of all the members from the
following sets:
(16.1) — if f is a constructor,
(16.1.1) — the sets of potential exceptions of the constructor invocations
(16.1.1.1) — for X's non-variant non-static data members,
(16.1.1.2) — for X's direct base classes, and
(16.1.1.3) — if X is non-abstract (10.4), for X's virtual base classes,
(including default argument expressions used in such invocations) as selected
by overload resolution for the implicit definition of f (12.1). [
Note: Even though destructors for fully-constructed subobjects are
invoked when an exception is thrown during the execution of a constructor
(15.2), their exception specifications do not contribute to the exception
specification of the constructor, because an exception thrown from such a
destructor could never escape the constructor (15.1, 15.5.1). — end
note]
(16.1.2) — the sets of potential exceptions of the initialization of
non-static data members from brace-or-equal-initializers that are not
ignored (12.6.2);
(16.2) — if f is an assignment operator, the sets of potential
exceptions of the assignment operator invocations for X's non-variant
non-static data members and for X's direct base classes (including
default argument expressions used in such invocations), as selected by overload
resolution for the implicit definition of f (12.8);
(16.3) — if f is a destructor, the sets of potential exceptions
of the destructor invocations for X's non-variant non-static data
members and for X's virtual and direct base classes.
17 An implicitly-declared special member function (Clause 12) is considered to
have an implicit exception specification, as follows, where S is the
set of potential exceptions of the implicitly-declared member function:
(17.1) — if S contains the pseudo-type "any", the
implicit exception specification is the set of all types;
(17.2) — otherwise, the implicit exception specification contains all the
types in S.
[ Note: An instantiation of an inheriting constructor template has an
implied exception specification as if it were a non-template inheriting
constructor. — end note ]
[ Example:
struct A {A(int = (A(5), 0)) noexcept;A(const A&) throw();A(A&&) throw();~A() throw(X);};struct B {B() throw();B(const B&) = default; // exception specification contains no typesB(B&&, int = (throw Y(), 0)) noexcept;~B() throw(Y);};int n = 7;struct D : public A, public B {int * p = new (std::nothrow) int[n];// exception specification of D::D() contains X and std::bad_array_new_length// exception specification of D::D(const D&) contains no types// exception specification of D::D(D&&) contains Y// exception specification of D::~D() contains X and Y};
Furthermore, if A::~A() or B::~B() were virtual,
D::~D() would not be as restrictive as that of A::~A, and the
program would be ill-formed since a function that overrides a virtual function
from a base class shall have an exception-specification at least as
restrictive as that in the base class. — end example ]
18 A deallocation function (3.7.4.2) with no explicit
exception-specification has an exception specification that is the empty set.
19 An exception-specification is considered to be needed when:
(19.1) — in an expression, the function is the unique lookup result or
the selected member of a set of overloaded functions (3.4, 13.3, 13.4);
(19.2) — the function is odr-used (3.2) or, if it appears in an
unevaluated operand, would be odr-used if the expression were
potentially-evaluated;
(19.3) — the exception-specification is compared to that of
another declaration (e.g., an explicit specialization or an overriding virtual
function);
(19.4) — the function is defined; or
(19.5) — the exception-specification is needed for a defaulted
special member function that calls the function. [ Note: A defaulted
declaration does not require the exception-specification of a base
member function to be evaluated until the implicit
exception-specification of the derived function is needed, but an
explicit exception-specification needs the implicit
exception-specification to compare against. — end note ]
The exception-specification of a defaulted special member function is
evaluated as described above only when needed; similarly, the
exception-specification of a specialization of a function template or
member function of a class template is instantiated only when needed.
20 In a dynamic-exception-specification, a type-id followed by an
ellipsis is a pack expansion (14.5.3).
21 [ Note: The use of dynamic-exception-specifications is
deprecated (see Annex D). — end note ]
1 The exception specification of a function is a predicate indicating whether the function might exit via an exception. If the predicate evaluates as false, the function is said to have a non-throwing exception specification. The exception specification is either defined explicitly by using an exception-specification as a suffix of a function declaration's declarator (8.3.5) or implicitly.
exception-specification: noexcept ( constant-expression ) noexcept throw ( )
2 In an exception-specification, the constant-expression, if supplied, shall be a constant expression (5.20) that is contextually converted to bool (Clause 4). A ( token that follows noexcept is part of the exception-specification and does not commence an initializer (8.5). The exception-specification noexcept (without a const-expression) is equivalent to the exception-specification noexcept(true). The exception-specification throw() is deprecated (see Annex D), and equivalent to the exception-specification noexcept(true).
3 An exeption specification is said to allow exceptions if the predicate evaluates to false. Otherwise, it does not allow exceptions.
4 If any declaration of a function has an exception specification that does not allow exceptions, all declarations, including the definition and any explicit specialization, of that function shall have an exception specification that does not allow exceptions. If any declaration of a pointer to function, reference to function, or pointer to member function has an exception specification that does not allow exceptions, all occurrences of that declaration shall have an exception-specification that does not allow exceptions. If a declaration of a function has an implicit exception specification, other declarations of the function shall not specify an exception-specification. 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 allows exceptions only if the exception-specifications of all other declarations of that function allow exceptions. A diagnostic is required only if the exception-specifications are not compatible within a single translation unit.
5 If a virtual function has an exception specification that does not allow exceptions, all declarations, including the definition, of any function that overrides that virtual function in any derived class shall not allow exceptions, unless the overriding function is defined as deleted. [ Example:
struct B { virtual void f() noexcept; virtual void g(); virtual void h() noexcept; }; struct D: B { void f(); // ill-formed void g() noexept; // OK void h() = delete; // OK };
The declaration of D::f is ill-formed because it allows all exceptions, whereas B::f allows no exceptions. — end example]
6 Whenever an exception is thrown and the search for a handler (15.3) encounters the outermost block of a function with an exception specification that does not allow exceptions, the function std::terminate() is called (15.5.1).
7 An implementation shall not reject an expression merely because when executed it throws or might throw an exception that the containing function does not allow. [ Example:
extern void f() noexcept; void g() { f(); // OK }
the call to f is well-formed even though, when called, f might throw an exception that g does not allow. — end example ]
8 An expression allows an exception if and only if any of its immediate subexpressions allows an exception. A subexpression e1 of an expression e is an immediate subexpression if there is no subexpression e2 of e such that e1 is a subexpression of e2.
9 If e initializes an object of type D using an inherited constructor for a class of type B (12.6.3), the set of immediate subexpressions of e includes the implied constructor invocations for subobjects of D that are not subobjects of B (including default argument expressions used in such invocations) as selected by overload resolution, and the initialization of non-static data members from brace-or-equal-initializers (12.6.2).
10 An expression (or subexpression) e does not allow exceptions unless
(10.1) e invokes a function, or member function, whose exception specification allows exceptions, or
(10.2) e implicitly invokes a function (such as an overloaded operator, an allocation function in a new-expression, a constructor for a function argument, or a destructor if e is a full-expression (1.9)), that allows exceptions, or
(10.3) e is a throw-expression (5.17), or
(10.4) e is a dynamic_cast expression that casts to a reference type and requires a run-time check (5.2.7), or
(10.5) e is a typeid expression applied to a glvalue expression whose type is a polymorphic class type (5.2.8), or
(10.6) e is a new-expression with a non-constant expression in the noptr-new-declarator (5.3.4), or
(10.7) any of the immediate subexpressions of e allows exceptions.
11 An implicitly declared special member function that is a constructor for a class X has an exception specification that does not allow exceptions unless any of the constructors selected by overload resolution in the implicit definition of that constructor to initialize direct base classes and non-static members, and, unless X is abstract, to initialize virtual base classes, has an exception specification that allows exceptions, or the invocation of such initialization contains an expression that allows exceptions, such as default argument expressions used in such invocations, or brace-or-equal-initializers for non-static data members.
12 [ Note: Even though destructors for fully-constructed subobjects are invoked when an exception is thrown during the execution of a constructor (15.2), their exception specifications do not contribute to the exception specification of the constructor, because an exception thrown from such a destructor could never escape the constructor (15.1, 15.5.1). — end note]
13 [ Note: An instantiation of an inheriting constructor template has an implied exception specification as if it were a non-template inheriting constructor. — end note ]
[ Example:
struct A { A(int = (A(5), 0)) noexcept; A(const A&) noexcept; A(A&&) noexcept; ~A(); }; struct B { B() throw(); B(const B&) = default; // implicit exception specification is true B(B&&, int = (throw Y(), 0)) noexcept; ~B() noexcept(false); }; int n = 7; struct D : public A, public B { int * p = new (std::nothrow) int[n]; // D::D() allows exceptions, as the new operator may throw bad_array_new_length // D::D(const D&) does not allow exceptions // D::D(D&&) allows exceptions, as the default argument for B's constructor may throw // D::~D() allows exceptions };
Furthermore, if A::~A() were virtual, the exception specification of D::~D() would not be as restrictive as that of A::~A, and the program would be ill-formed since a function that overrides a virtual function from a base class shall have an exception-specification at least as restrictive as that in the base class. — end example ]
14 An implicitly declared special member function that is the destructor for a class X has an exception specification that does not allow exceptions unless any of the destructors for its direct or virtual base classes and non-variant non-static data members has a destructor with an exception specification that allows exceptions.
15 An implicitly declared special member function f that is an assignment operator for a class X has an exception specification that does not allow exceptions unless the implicit definition of f selects by overload resolution an assignment operator from a direct base class, or from a non-variant non-static data members, that allows exceptions.
16 A function with an implied non-throwing exception specification, where the function’s type is declared to be T, is instead considered to be of type "noexcept T".
17 A deallocation function (3.7.4.2) with no explicit exception-specification has an exception specification that does not allow exceptions.
18 An exception-specification is considered to be needed when:
(18.1) — in an expression, the function is the unique lookup result or the selected member of a set of overloaded functions (3.4, 13.3, 13.4);
(18.2) — the function is odr-used (3.2) or, if it appears in an unevaluated operand, would be odr-used if the expression were potentially-evaluated;
(18.3) — the exception-specification is compared to that of another declaration (e.g., an explicit specialization or an overriding virtual function);
(18.4) — the function is defined; or
(18.5) — the exception-specification is needed for a defaulted special member function that calls the function. [ Note: A defaulted declaration does not require the exception-specification of a base member function to be evaluated until the implicit exception-specification of the derived function is needed, but an explicit exception-specification needs the implicit exception-specification to compare against. — end note ]
The exception-specification of a defaulted special member function is evaluated as described above only when needed; similarly, the exception-specification of a specialization of a function template or member function of a class template is instantiated only when needed.
Thanks to Jens Maurer for the initial review, identifying a number of proposed edits that had unintended consequences.