Question: C2694 on destructor when base class' member's destructor has non-empty noexcept specifier and a body

Question

C2694 on destructor when base class' member's destructor has non-empty noexcept specifier and a body

Answers 1
Added at 2016-12-30 21:12
Tags
Question

This is my first time posting on Stack Overflow. Please let me know how I might improve in the way I should post questions in the future.

I am experiencing a compiler error that I can't explain and have not been able to find information online about it. I've recently added a noexcept specifier to the destructor of a wrapper class and now a large number of classes which inherit from classes that use this wrapper are failing to compile. I've tried it with GCC 4.9 without compiler error.

I'm using Visual Studio Professional 2015 Version 14.0.25431.01 Update 3

Consider the following minimal code which reproduces the problem :

#include <type_traits>

template<class T>
struct member
{
    ~member() noexcept(std::is_nothrow_destructible<T>::value) {};
};

struct parent
{ 
    virtual ~parent() noexcept = default;
};


struct derived : public parent
{
    member<int> x;
};

The snippet produces the following error message :

1>c:\users\fandrieux\workspace\tmp\dtor_noexcept\main.cpp(19): error C2694: 'derived::~derived(void) noexcept(<expr>)': overriding virtual function has less restrictive exception specification than base class virtual member function 'parent::~parent(void) noexcept'
1>  c:\users\fandrieux\workspace\tmp\dtor_noexcept\main.cpp(19): note: compiler has generated 'derived::~derived' here
1>  c:\users\fandrieux\workspace\tmp\dtor_noexcept\main.cpp(12): note: see declaration of 'parent::~parent'

What I find interesting is that the compiler error disappears if you replace member's destructor body by = default or use either noexcept or noexcept(true) :

// None of these produce compiler errors
virtual ~member() noexcept(std::is_nothrow_destructible<T>::value) = default;
virtual ~member() noexcept(true) {}
virtual ~member() noexcept {}

I know that it's destructor does not throw. Paranoids and skeptics (like me) can add the following static assert and it check themselves :

static_assert(std::is_nothrow_destructible<T>::value, "Might throw!");

According to MSDN it signals an insufficient dynamic exception specifier. How does this apply here? Isn't noexcept([boolean expression]) equivalent to either noexcept(true) or noexcept(false)? Why does this change depending on the presence of a function body? Adding an explicit noexcept destructor to derived gets ride of the compiler error, but this feels like an unnecessary workaround. In practice it's also quite a burden when you consider that every derived class would have to be updated.

Answers
nr: #1 dodano: 2016-12-30 22:12

This looks like a compiler bug. If we add the following class:

struct dtor_throws
{
    ~dtor_throws() noexcept(false) {}
};

And change the definition of derived thusly:

struct derived : public parent
{
    member<dtor_throws> x;
};

Then GCC and Clang both complain that the exception specification of ~derived is looser than ~parent.

In the original example, MSVC appears not to be embedding the value of the noexcept expression in the type of ~parent, but merely treating all compound noexcept specifications of user-defined destructors of class templates as being more relaxed than noexcept(true).

MSVC 2017 RC is also affected.

Source Show
◀ Wstecz