Search code examples
c++implicit-declaration

Implicit exception specification differs for inline and out-of-line definition?


Consider the following example:

#include <iostream>

struct X
{
    X() = default;
    X(const X&) = default;
    X(X&&) = default;
    X& operator = (const X&) = default;
    X& operator = (X&&) = default;
};

int main()
{
    static_assert(std::is_nothrow_move_assignable_v<X>);

    return 0;
}

The move assignment operator appears to be implicitly noexcept, since the static assertion passes.

However, defining it out-of-line makes the assertion fail:

#include <iostream>

struct X
{
    X() = default;
    X(const X&) = default;
    X(X&&) = default;
    X& operator = (const X&) = default;
    X& operator = (X&&); //= default;
};

X& X::operator=(X&&) = default;

int main()
{
    static_assert(std::is_nothrow_move_assignable_v<X>);

    return 0;
}

Why do they behave differently?


Solution

  • In your second code snippet, the move-assignment function is actually user-provided, because you have (explicitly) declared it – but not defaulted it – in its first declaration.

    From this Draft C++17 Standard (bold emphasis mine):

    11.4.2 Explicitly-defaulted functions        [dcl.fct.def.default]



    5    … A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed.

    To get the same behaviour (i.e., to make that 'defaulted' function noexcept) as in the first snippet, but without specifying the = default in the declaration, you will need to explicitly add the noexcept attribute, both to the declaration and to the later definition:

    struct X
    {
        X() = default;
        X(const X&) = default;
        X(X&&) = default;
        X& operator = (const X&) = default;
        X& operator = (X&&) noexcept; // Add "noxcept" here ...
    };
    
    
    X& X::operator=(X&&) noexcept = default; // ... AND here!
    
    int main()
    {
        static_assert(std::is_nothrow_move_assignable_v<X>);
    
        return 0;
    }