Title
Problems with implicitly-declared exception-specifications
Status
cd4
Section
14.5 [except.spec]
Submitter
Richard Smith

Created on 2011-08-16.00:00:00 last changed 87 months ago

Messages

Date: 2014-11-15.00:00:00

[Moved to DR at the November, 2014 meeting.]

Date: 2014-06-15.00:00:00

Proposed resolution (June, 2014):

  1. Change 14.5 [except.spec] paragraph 5 as follows:
  2. 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:...
  3. Add the following new paragraphs following 14.5 [except.spec] paragraph 13:

  4. An exception-specification is not considered part of a function's type.

    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.

    The set of potential exceptions of a function, function pointer, or member function pointer f is defined as follows:

    • If the declaration of f has a non-throwing exception-specification, the set is empty.

    • Otherwise, if the declaration of f has a dynamic-exception-specification, the set consists of every type in that dynamic-exception-specification.

    • Otherwise, the set consists of the pseudo-type “any”.

    The set of potential exceptions of an expression e is empty if e is a core constant expression (7.7 [expr.const]). 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:

    • If e is a function call (7.6.1.3 [expr.call]):

      • If its postfix-expression is a (possibly parenthesized) id-expression (_N4567_.5.1.1 [expr.prim.general]), class member access (7.6.1.5 [expr.ref]), or pointer-to-member operation (7.6.4 [expr.mptr.oper]) 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).

      • Otherwise, S contains the pseudo-type “any”.

    • 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), S is the set of potential exceptions of the function.

    • if e is a throw-expression (14.2 [except.throw]), S consists of the type of the exception object that would be initialized by the operand, if present, or the pseudo-type “any” otherwise.

    • if e is a dynamic_cast expression that casts to a reference type and requires a run-time check (7.6.1.7 [expr.dynamic.cast]), S consists of the type std::bad_cast.

    • if e is a typeid expression applied to a glvalue expression whose type is a polymorphic class type (7.6.1.8 [expr.typeid]), S consists of the type std::bad_typeid.

    • if e is a new-expression with a non-constant expression in the noptr-new-declarator (7.6.2.8 [expr.new]), 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:

    • for f(), the set consists of int;

    • for g(), the set consists of “any”;

    • for new A, the set consists of “any”;

    • for B(), the set is empty;

    • for new D, the set consists of “any” and double.

    end example]

    Given a member function f of some class X, where f is an inheriting constructor (_N4527_.12.9 [class.inhctor]) or an implicitly-declared special member function, the set of potential exceptions of the implicitly-declared member function f consists of all the members from the following sets:

    • if f is a constructor,

      • the sets of potential exceptions of the constructor invocations

        • for X's non-variant non-static data members,

        • for X's direct base classes, and

        • if X is non-abstract (11.7.4 [class.abstract]), 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 (11.4.5 [class.ctor]). [Note: Even though destructors for fully-constructed subobjects are invoked when an exception is thrown during the execution of a constructor (14.3 [except.ctor]), 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 (14.2 [except.throw], 14.6.2 [except.terminate]). —end note]

      • the sets of potential exceptions of the initialization of non-static data members from brace-or-equal-initializers that are not ignored (11.9.3 [class.base.init]);

    • 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 (11.4.5.3 [class.copy.ctor]);

    • 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.

  5. Change 14.5 [except.spec] paragraph 14 as follows:

  6. An inheriting constructor (_N4527_.12.9 [class.inhctor]) and an implicitly declared implicitly-declared special member function (11.4.4 [special]) are considered to have an implicit exception-specification, as follows, where f is the member function and S is the set of potential exceptions of the implicitly-declared member function f:.

    • if S contains the pseudo-type “any”, the implicit exception-specification is noexcept(false);

    • otherwise, if S contains at least one type, the implicit exception-specification specifies each type T contained in S;

    • otherwise, the implicit exception-specification is noexcept(true).

    If f is an inheriting constructor or an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f's implicit definition; f allows all exceptions if any function it directly invokes allows all exceptions, and f has the exception-specification noexcept(true) if every function it directly invokes allows no exceptions. [Note: It follows that f has the exception-specification noexcept(true) if it invokes no other functions. —end note] [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; // Declaration of B::B(const B&) noexcept(true)
        B(B&&, int = (throw Y(), 0)) throw(Y) noexcept;
        ~B() throw(Y);
      };
      int n = 7;
      struct D : public A, public B {
        int * p = new (std::nothrow) int[n];
        // Implicit declaration of D::D() throw(X, std::bad_array_new_length);
        // Implicit declaration of D::D();
        // Implicit declaration of D::D(const D&) noexcept(true);
        // Implicit declaration of D::D(D&&) throw(Y);
        // Implicit declaration of D::~D() throw(X, Y);
      };
    
  7. Change 7.6.2.7 [expr.unary.noexcept] paragraph 3 as follows:

  8. The result of the noexcept operator is false true if in a potentially-evaluated context the set of potential exceptions of the expression would contain is empty, and false otherwise.

    • a potentially-evaluated call83 to a function, member function, function pointer, or member function pointer that does not have a non-throwing exception-specification (14.5 [except.spec]), unless the call is a constant expression (7.7 [expr.const]),

    • a potentially-evaluated throw-expression (14.2 [except.throw]),

    • a potentially-evaluated dynamic_cast expression dynamic_cast<T>(v), where T is a reference type, that requires a run-time check (7.6.1.7 [expr.dynamic.cast]), or

    • a potentially-evaluated typeid expression (7.6.1.8 [expr.typeid]) applied to a glvalue expression whose type is a polymorphic class type (11.7.3 [class.virtual]).

    Otherwise, the result is true.

This resolution also resolves issues 1356, 1465, and 1639.

Date: 2014-07-07.00:00:00

Additional note, May, 2014:

The current version of the proposed resolution only defines the set of potential exceptions for special member functions; since an inheriting constructor is not a special member function, the exception-specification for an inheriting constructor is no longer specified.

In addition, the structure of the specification of the set of potential exceptions of an expression is unclear. If the bulleted list is intended to be the definition of general statement (“union of all sets of potential exceptions...”), it's incomplete because it doesn't consider exceptions thrown by the evaluation of function arguments in a call, just the exceptions thrown by the function itself; if it's intended to be a list of exceptions to the general rule, the rule about core constant expressions doesn't exclude unselected subexpressions that might throw, so those exceptions are incorrect included in the union.

The issue has been returned to "review" status to allow discussion of these points.

See also the discussion in messages 25290 through 25293.

Date: 2014-02-15.00:00:00

Proposed resolution, February, 2014 [SUPERSEDED]:

  1. Change 14.5 [except.spec] paragraph 5 as follows:

  2. 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:...
  3. Add the following two new paragraphs and change 14.5 [except.spec] paragraph 14 as indicated:

  4. A set of potential exceptions may contain types and the special value “any”. The set of potential exceptions of an expression is the union of all sets of potential exceptions of each potentially-evaluated subexpression e:

    • If e is a core constant expression (7.7 [expr.const]), the set is empty.

    • Otherwise, if e is a function call (7.6.1.3 [expr.call]) whose postfix-expression is not a (possibly parenthesized) id-expression (_N4567_.5.1.1 [expr.prim.general]) or class member access (7.6.1.5 [expr.ref]), the set consists of “any”.

    • Otherwise, if e invokes a function, member function, or function pointer (including implicit calls, such as to an overloaded operator or to an allocation function in a new-expression):

      • if its declaration has a non-throwing exception-specification, the set is empty;

      • otherwise, if its declaration has a dynamic-exception-specification, the set consists of every type in that dynamic-exception-specification;

      • otherwise, the set consists of “any”.

    • If e is a throw-expression (14.2 [except.throw]), the set consists of the type of the exception object that would be initialized by the operand if present, or “any” otherwise.

    • If e is a dynamic_cast expression that casts to a reference type and requires a run-time check (7.6.1.7 [expr.dynamic.cast]), the set consists of the type std::bad_cast.

    • If e is a typeid expression applied to a glvalue expression whose type is a polymorphic class type (7.6.1.8 [expr.typeid]), the set consists of the type std::bad_typeid.

    • If e is a new-expression with a non-constant expression in the noptr-new-declarator (7.6.2.8 [expr.new]), the set also includes the type std::bad_array_new_length.

    • If none of the previous items applies, the set is the empty set.

    The set of potential exceptions of an implicitly-declared special member function f of some class X is defined as follows:

    • If f is a constructor, the set is the union of the sets of potential exceptions of the constructor invocations for X's non-variant non-static data members, for X's direct base classes, and, if X is non-abstract (11.7.4 [class.abstract]), for X's virtual base classes, as selected by overload resolution for the implicit definition of f (11.4.5 [class.ctor]), including default argument expressions used in such invocations. [Note: Even though destructors for fully constructed subobjects are invoked when an exception is thrown during the execution of a constructor (14.3 [except.ctor]), 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 (14.2 [except.throw], 14.6.2 [except.terminate]). —end note]

    • If f is a default constructor, the set also contains all members of the sets of potential exceptions of the initialization of non-static data members from brace-or-equal-initializers.

    • If f is an assignment operator, the set is the union of the sets of potential exceptions of the assignment operator invocations for X's non-variant non-static data members and for X's virtual and direct base classes, as selected by overload resolution for the implicit definition of f (11.4.5.3 [class.copy.ctor]), including default argument expressions used in such invocations.

    • If f is a destructor, the set is the union of 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.

    An inheriting constructor (_N4527_.12.9 [class.inhctor]) and an implicitly-declared special member function (11.4.4 [special]) have are considered to have an implicit exception-specification. If f is an inheriting constructor or an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f's implicit definition; f allows all exceptions if any function it directly invokes allows all exceptions, and f has the exception-specification noexcept(true) if every function it directly invokes allows no exceptions. [Note: It follows that f has the exception-specification noexcept(true) if it invokes no other functions. —end note] [Note: An instantiation of an inheriting constructor template has an implied exception-specification as if it were a non-template inheriting constructor. —end note] The implicit exception-specification is noexcept(false) if the set of potential exceptions of the special member function contains “any”; otherwise, if that set contains at least one type, the implicit exception-specification specifies each type T contained in the set; otherwise, the implicit exception-specification is noexcept(true). [Example:

      struct A {
        A();
        A(const A&) throw();
        A(A&&) throw();
        ~A() throw(X);
      };
      struct B {
        B() throw();
        B(const B&) = default; // Declaration of B::B(const B&) noexcept(true) throw();
        B(B&&, int = (throw Y(), 0)) throw(Y) noexcept;
        ~B() throw(Y);
      };
      struct D : public A, public B {
          // Implicit declaration of D::D();
          // Implicit declaration of D::D(const D&) noexcept(true);
          // Implicit declaration of D::D(D&&) throw(Y);
          // Implicit declaration of D::~D() throw(X, Y);
      };
    

    Furthermore...

  5. Change 7.6.2.7 [expr.unary.noexcept]paragraph 3 as follows:

  6. The result of the noexcept operator is false true if in a potentially-evaluated context the set of potential exceptions of the expression (14.5 [except.spec]) would contain is empty, and false otherwise.

    • a potentially-evaluated call [[Footnote: This includes implicit calls such as the call to an allocation function in a new-expression. —end footnote] to a function, member function, function pointer, or member function pointer that does not have a non-throwing exception-specification (14.5 [except.spec]), unless the call is a constant expression (7.7 [expr.const]),

    • a potentially-evaluated throw-expression (14.2 [except.throw]),

    • a potentially-evaluated dynamic_cast expression dynamic_cast<T>(v), where T is a reference type, that requires a run-time check (7.6.1.7 [expr.dynamic.cast]), or

    • a potentially-evaluated typeid expression (7.6.1.8 [expr.typeid]) applied to a glvalue expression whose type is a polymorphic class type (11.7.3 [class.virtual]).

    Otherwise, the result is true.

    (This resolution also resolves issues 1356, 1465, and 1639.)

Date: 2014-07-07.00:00:00

Additional note, April, 2013:

The version of this resolution approved in Bristol assumed the underlying text of the C++11 IS; however, the wording of 14.5 [except.spec] paragraph 14 has been changed by previous resolutions, so this and the related issues are being returned to "review" status.

Date: 2013-05-03.00:00:00

Additional note, April, 2013:

One problem with the approach suggested in the preceding note would be something like the following example:

  struct S {
    virtual ~S() throw(int);
  };
  struct D: S { };

This approach would make the example ill-formed, because the derived class destructor would be declared to throw types not permitted by the base class destructor's exception-specification. A further elaboration on the suggestion above that would not have this objection would be to define all dynamic-exception-specifications as simply equivalent to noexcept(false).

(See also issue 1639.)

Date: 2013-03-15.00:00:00

Additional note (March, 2013):

It has been suggested that it might be more consistent with other parts of the language, and particularly in view of the deprecation of dynamic-exception-specifications, if a potentially-throwing non-static data member initializer simply made an implicit constructor noexcept(false) instead of giving it a set of potential exception types.

Date: 2012-10-15.00:00:00

Additional note (October, 2012):

The preceding wording has been modified from the version that was reviewed following the October, 2012 meeting and thus has been returned to "review" status.

Date: 2012-10-15.00:00:00

Proposed resolution (October, 2012) [SUPERSEDED]:

  1. Add the following two new paragraphs and make the indicated changes to 14.5 [except.spec] paragraph 14:

  2. A set of potential exceptions may contain types and the special value “any.” The set of potential exceptions of an expression is the union of all sets of potential exceptions of each potentially-evaluated subexpression e:

    • If e is a call to a function, member function, function pointer, or member function pointer (including implicit calls, such as a call to the allocation function in a new-expression):

      • if it has a non-throwing exception-specification or the call is a core constant expression (7.7 [expr.const]), the set is empty;

      • otherwise, if it has a dynamic-exception-specification, the set consists of every type in that dynamic-exception-specification;

      • otherwise, the set consists of “any.”

    • If e is a throw-expression (14.2 [except.throw]), the set consists of the type of the exception object that would be initialized by the operand if present, or “any” otherwise.

    • If e is a dynamic_cast expression that casts to a reference type and requires a run-time check (7.6.1.7 [expr.dynamic.cast]), the set consists of the type std::bad_cast.

    • If e is a typeid expression applied to a glvalue expression whose type is a polymorphic class type (7.6.1.8 [expr.typeid]), the set consists of the type std::bad_typeid.

    • If e is a new-expression with a non-constant expression in the noptr-new-declarator (7.6.2.8 [expr.new]), the set also includes the type std::bad_array_new_length.

    • Otherwise, the set is the empty set.

    The set of potential exceptions of a function f of some class X, where f is an inheriting constructor or an implicitly-declared special member function, is defined as follows:

    • If f is a constructor, the set is the union of the sets of potential exceptions of the constructor invocations for X's non-variant non-static data members, for X's direct base classes, and, if X is non-abstract (11.7.4 [class.abstract]), for X's virtual base classes, as selected by overload resolution for the implicit definition of f (11.4.5 [class.ctor]), including default argument expressions used in such invocations. [Note: Even though destructors for fully constructed subobjects are invoked when an exception is thrown during the execution of a constructor (14.3 [except.ctor]), 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 (14.2 [except.throw], 14.6.2 [except.terminate]). —end note]

    • If f is a default constructor or inheriting constructor, the set also contains all members of the sets of potential exceptions of the initialization of non-static data members from brace-or-equal-initializers.

    • If f is an assignment operator, the set is the union of the sets of potential exceptions of the assignment operator invocations for X's non-variant non-static data members and for X's virtual and direct base classes, as selected by overload resolution for the implicit definition of f (11.4.5.3 [class.copy.ctor]), including default argument expressions used in such invocations.

    • If f is a destructor, the set is the union of 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.

    An inheriting constructor (_N4527_.12.9 [class.inhctor]) and an implicitly declared implicitly-declared special member function (11.4.4 [special]) have an are considered to have an implicit exception-specification. If f is an inheriting constructor or an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f's implicit definition; f allows all exceptions if any function it directly invokes allows all exceptions, and f has the exception-specification noexcept(true) if every function it directly invokes allows no exceptions. The implicit exception-specification is noexcept(false) if the set of potential exceptions of the function contains “any;” otherwise, if that set contains at least one type, the implicit exception-specification specifies each type T contained in the set; otherwise, the implicit exception-specification is noexcept(true). [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();
        A(const A&) throw();
        A(A&&) throw();
        ~A() throw(X);
      };
      struct B {
        B() throw();
        B(const B&) throw();
        B(B&&, int = (throw Y(), 0)) throw(Y) noexcept;
        ~B() throw(Y);
      };
      struct D : public A, public B {
          // Implicit declaration of D::D();
          // Implicit declaration of D::D(const D&) noexcept(true);
          // Implicit declaration of D::D(D&&) throw(Y);
          // Implicit declaration of D::~D() throw(X, Y);
      };
    

    Furthermore, if...

  3. Change 7.6.2.7 [expr.unary.noexcept] paragraph 3 as follows:

  4. The result of the noexcept operator is false if in a potentially-evaluated context the set of potential exceptions of the expression (14.5 [except.spec]) would contain contains “any” or at least one type and true otherwise.

    • a potentially evaluated call80 to a function, member function, function pointer, or member function pointer that does not have a non-throwing exception-specification (14.5 [except.spec]), unless the call is a constant expression (7.7 [expr.const]),

    • a potentially evaluated throw-expression (14.2 [except.throw]),

    • a potentially evaluated dynamic_cast expression dynamic_cast<T>(v), where T is a reference type, that requires a run-time check (7.6.1.7 [expr.dynamic.cast]), or

    • a potentially evaluated typeid expression (7.6.1.8 [expr.typeid]) applied to a glvalue expression whose type is a polymorphic class type (11.7.3 [class.virtual]).

    Otherwise, the result is true.

(This resolution also resolves issues 1356 and 1465.)

Date: 2012-08-15.00:00:00

Additional note (August, 2012):

The direction established by CWG for resolving this issue was to consider functions called from default arguments and non-static data member initializers in determining the exception-specification. This leads to a problem with ordering: because non-static data member initializers can refer to members declared later, their effect cannot be known until the end of the class. However, a non-static data member initializer could possibly refer to an implicitly-declared constructor, either its own or that of an enclosing class.

Date: 2011-08-16.00:00:00

The determination of the exception-specification for an implicitly-declared special member function, as described in 14.5 [except.spec] paragraph 14, does not take into account the fact that nonstatic data member initializers and default arguments in default constructors can contain throw-expressions, which are not part of the exception-specification of any function that is “directly invoked” by the implicit definition. Also, the reference to “directly invoked” functions is not restricted to potentially-evaluated expressions, thus possibly including irrelevant exception-specifications.

History
Date User Action Args
2017-02-06 00:00:00adminsetstatus: drwp -> cd4
2015-05-25 00:00:00adminsetstatus: dr -> drwp
2015-04-13 00:00:00adminsetmessages: + msg5423
2014-11-24 00:00:00adminsetstatus: ready -> dr
2014-07-07 00:00:00adminsetmessages: + msg5074
2014-07-07 00:00:00adminsetstatus: review -> ready
2014-05-27 00:00:00adminsetmessages: + msg4994
2014-05-27 00:00:00adminsetstatus: ready -> review
2014-03-03 00:00:00adminsetstatus: review -> ready
2013-05-03 00:00:00adminsetmessages: + msg4326
2013-05-03 00:00:00adminsetmessages: + msg4325
2013-05-03 00:00:00adminsetmessages: + msg4324
2013-03-18 00:00:00adminsetmessages: + msg4254
2012-11-03 00:00:00adminsetmessages: + msg4089
2012-11-03 00:00:00adminsetmessages: + msg4088
2012-11-03 00:00:00adminsetstatus: drafting -> review
2012-09-24 00:00:00adminsetmessages: + msg3904
2011-08-16 00:00:00admincreate