C2694 на деструкторе, когда деструктор элемента базового класса имеет непустой спецификатор noexcept и тело

Я испытываю ошибку компилятора, которую я не могу объяснить и не смог найти информацию в Интернете об этом. Недавно я добавил спецификатор noexcept для деструктора класса-оболочки, и теперь большое количество классов, которые наследуются от классов, использующих эту оболочку, не скомпилируются. Я пробовал его с GCC 4.9 без ошибок компилятора.

Я использую Visual Studio Professional 2015 версии 14.0.25431.01 Update 3

Рассмотрим следующий минимальный код, который воспроизводит проблему:

#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;
};

В этом фрагменте отображается следующее сообщение об ошибке:

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'

Интересным является то, что ошибка компилятора исчезает, если вы замените тело деструктора элемента на = default или используйте либо noexcept, либо 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 {}

Я знаю, что деструктор не бросает. Параноиды и скептики (например, я) могут добавить следующее статическое утверждение и проверить себя:

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

В соответствии с MSDN он сигнализирует о недостаточном динамическом спецификаторе исключений. Как это применимо здесь? Не является ли noexcept ([boolean expression]) эквивалентным либо noexcept(true), либо noexcept(false)? Почему это изменяется в зависимости от наличия тела функции? Добавление явного noexcept деструктора в производную получает езду ошибки компилятора, но это кажется ненужным обходным путем. На практике это также довольно тяжело, если учесть, что каждый производный класс должен быть обновлен.

Ответы

Ответ 1

Это похоже на ошибку компилятора. Если мы добавим следующий класс:

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

И измените определение derived следующим образом:

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

Затем GCC и Clang жалуются, что спецификация исключения ~derived меньше, чем ~parent.

В исходном примере MSVC, похоже, не встраивает значение выражения noexcept в тип ~parent, а просто обрабатывает спецификации всех составных noexcept определяемых пользователем деструкторов шаблонов классов как более менее noexcept(true).

MSVC 2017 RC также затронут.