This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 108h. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2022-07-01


1351. Problems with implicitly-declared exception-specifications

Section: 14.5  [except.spec]     Status: CD4     Submitter: Richard Smith     Date: 2011-08-16

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

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.

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.

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:

    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:

    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.

    Otherwise, the result is true.

(This resolution also resolves issues 1356 and 1465.)

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.

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.

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

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.

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:

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

    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.

    Otherwise, the result is true.

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

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.

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:

    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:

    [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:

    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:

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

    Otherwise, the result is true.

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