c++language-lawyerdestructorcompiler-bugnoexcept

Should std::variant be nothrow destructible when its alternative has potentially throwing destructor?


TL;DR: see compilers disagree on code by Godbolt link: https://godbolt.org/z/f7G6PTEsh

Should std::variant be nothrow destructible when its alternative has potentially throwing destructor? Clang and GCC seem to disagree on it (probably different standard library implementations on Godbolt?), MSVC thinks it should. cppreference says variant's destructor does not have noexcept specifications, and in that case destructor should be unconditionally noexcept unless it has potentially throwing members or bases (https://en.cppreference.com/w/cpp/language/noexcept_spec), which is not specified for variant (duh!).

The reason I'm asking is not to mess with throwing stuff out of destructors, but to gradually refactor weird legacy stuff that is minimized to the following snippet ("exception specification of overriding function is more lax than the base version"), and also related bug on dated compiler that we can't update yet.

#include <type_traits>
#include <variant>

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

static_assert(std::is_nothrow_destructible_v<std::variant<S>>); // assertion failed with clang

struct Y
{
    virtual ~Y() {}
};

struct Z: public Y
{
    virtual ~Z() {}

    // clang error because of this member: 
    // exception specification of overriding function is more lax than the base version
    std::variant<int, S> member;
}

Solution

  • GCC is wrong. According to [res.on.exception.handling]/3:

    Destructor operations defined in the C++ standard library shall not throw exceptions. Every destructor in the C++ standard library shall behave as if it had a non-throwing exception specification.

    How the destructor of std::variant is declared is beside the point; the library implementor is responsible for ensuring that the destructor behaves as if it were noexcept, wherever the presence or absence of such noexceptness can be detected.

    Note that types used to instantiate standard library templates are not allowed to throw exceptions ([res.on.functions]/2.4) but that's not necessarily relevant to your example. The rule doesn't say that your S type must have a noexcept destructor. It just says that it's not allowed to actually throw. Violating this rule leads to UB, only if an execution that results in an exception escaping the destructor actually happens.