KnowItAllWannabe
KnowItAllWannabe

Reputation: 13512

What is the exception specification for a defaulted move operation defined outside the class?

Consider this class:

class C1 {
  C1(C1&&) = default;        // declare and define move ctor
};

Because C1's move ctor is explicitly defaulted on its first declaration, 8.4.2 of the Standard tells us it has the same exception specification (ES) as if the function had been implicitly declared. We can then use 15.4/14 and 12.8/15 to conclude that its ES is noexcept(true).

Now consider a class C2 that is the same, except its move ctor is defaulted outside the class definition:

class C2 {
  C2(C2&&);                 // declare move ctor
}; 

C2::C2(C2&&) = default;     // define move ctor

What is the ES for C2's move ctor? Because it's not defaulted on its first declaration, 8.4.2/2 doesn't apply. Because it doesn't have an explicit ES, 8.4.2/3 doesn't apply. Because it's not implicitly declared, 15.4/14 doesn't apply. From what I can tell, that means that 15.4/12 applies, and it says that the default function ES is noexcept(false).

If I'm right, that means that the move ctor in C1 is noexcept(true), but the conceptually identical move ctor in C2 is noexcept(false).

Is my reasoning about C2 correct?

Upvotes: 2

Views: 204

Answers (2)

Ville Voutilainen
Ville Voutilainen

Reputation: 1066

The interpretation is correct, yes. Defaulting after the first declaration means generating the function body without performing any magic on the declaration. If the definition is in a different translation unit, you can freely switch between

C2::C2(C2&& rhs) {/* hand-written implementation here */}

and

C2::C2(C2&& rhs) = default;

and that is not visible to the users of the class. Since you didn't default on the first declaration, the declaration is effectively noexcept(false) and (for better or worse) stays that way regardless of the subobjects in your class. Defaulting on the first declaration kicks in further "magic" in that the noexcept-specification of your declaration can also be generated.

Upvotes: 1

Howard Hinnant
Howard Hinnant

Reputation: 218900

Yes, your interpretation is correct, and if you make your declarations public, it is easy to show that both clang and gcc agree with your reasoning:

#include <type_traits>

class C1
{
public:
  C1(C1&&) = default;        // declare and define move ctor
};

class C2 {
public:
  C2(C2&&);                 // declare move ctor
}; 

C2::C2(C2&&) = default;     // define move ctor

int
main()
{
    static_assert(std::is_nothrow_move_constructible<C1>{}, "");
    static_assert(!std::is_nothrow_move_constructible<C2>{}, "");
}

Upvotes: 6

Related Questions