The compilers are caught in a dilemma here, for the following reasons:
(1) Not specifying any exceptions in a function declaration (i.e. not using throw
nor noexcept
(which is equivalent to noexcept(true)
)) means to allow that function to throw all possible exceptions:
(§15.4/12, emphasis mine) A function with no exception-specification or with an exception-specification of the form noexcept(constant-expression)
where the constant-expression
yields false
allows all exceptions. [...]
(2) A default destructor must allow exactly the exceptions that are allowed by the functions directly invoked by its implicit definition:
(§15.4/14, emphasis mine) An implicitly declared special member function (Clause 12) shall have an exception-specification. If f is 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 shall allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions if every function it directly invokes allows no exceptions.
(3) When a special member (such as a destructor) is explicitly defaulted, i.e. when you use = default
, the exception specification is optional (see the use of "may have" below):
(8.4.2/2, emphasis mine) An explicitly-defaulted function [...] may have an explicit exception-specification only if it is compatible (15.4) with the exception-specification on the implicit declaration. [...]
There is no statement in the Standard that requires the exception specification in an explicitly defaulted destructor.
Conclusion: Therefore, not specifiying the exceptions of an explicitly defaulted destructor can be interpreted in two ways:
- To mean that all exceptions are allowed (according to (1) above)
- Or, alternatively, to mean that exactly the same exceptions are allowed as are allowed by the implicit default definition of the destructor (according to (3) above), which in your case means to allow no exceptions (according to (2) above).
Unfortunately, GCC resolves this dilemma in one way (in favor of "no exceptions") in the case of your base class declaration, and in a different way in the case of the derived class (in favor of "all exceptions" there).
I believe the most natural interpretation of this admittedly ambiguous situation is to assume that (2) and (3) override (1). The Standard does not say so, but it should. Under that interpretation, Clang seems to be right here.