vsoftco
vsoftco

Reputation: 56577

How to make default constructor defined outside the class noexcept?

I know that a constructor marked as =default will "try" to be noexcept whenever possible. However, if I define it outside the class, it is not noexcept anymore, as you can see from this code:

#include <iostream>
#include <utility>
#include <type_traits>

struct Bar
{
    Bar() = default;
    Bar(Bar&&) = default; // noexcept
};

struct Foo
{
    Foo() = default;
    Foo(Foo&&);
};
// moving the definition outside makes it noexcept(false)
Foo::Foo(Foo&&) = default; // not noexcept anymore

int main()
{
    Foo foo;
    Bar bar;
    std::cout << std::boolalpha;
    // checks
    std::cout << std::is_nothrow_move_constructible<Bar>::value << std::endl;
    std::cout << std::is_nothrow_move_constructible<Foo>::value << std::endl;
}

How can I define such a =default constructor outside a class and make it noexcept? And why is such a constructor noexcept(false) if defined outside the class? This issue arises when implementing PIMPL via smart pointers.

Upvotes: 6

Views: 489

Answers (2)

Praetorian
Praetorian

Reputation: 109219

The rules governing the exception specification for your two examples are covered in §8.4.2/2 [dcl.fct.def.default]

... If a function is explicitly defaulted on its first declaration,
— it is implicitly considered to be constexpr if the implicit declaration would be,
it is implicitly considered to have the same exception-specification as if it had been implicitly declared (15.4), and
— ...

Bar's move constructor is noexcept(true) because in §15.4/14 [except.spec]

An implicitly declared special member function (Clause 12) shall have an exception-specification. If f is an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f's implicit definition; f shall allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions if every function it directly invokes allows no exceptions.

The rules in §8.4.2/2 do not apply to special member functions that have been explicitly defaulted after the initial declaration, except destructors, which are special cased in §12.4/3 to be noexcept(true) unless you declare it noexcept(false) or the destructors of one of the data members or base classes can throw.

Thus, unless you specify Foo(Foo&&) to be noexcept(true), it is assumed to be noexcept(false).

The reason you needed to add the noexcept specification to both the declaration and later explicit default declaration is found in §15.4

3   Two exception-specifications are compatible if:
— both are non-throwing (see below), regardless of their form,
— ...
4   If any declaration of a function has an exception-specification that is not a noexcept-specification allowing all exceptions, all declarations, including the definition and any explicit specialization, of that function shall have a compatible exception-specification. ...

Upvotes: 2

vsoftco
vsoftco

Reputation: 56577

I realized now that I can do this, it didn't cross my mind until now:

struct Foo
{
    Foo() = default;
    Foo(Foo&&) noexcept;
};
Foo::Foo(Foo&&) noexcept = default; // now it is noexcept

Still the second question Why is it noexcept(false) by default? applies.

Upvotes: 4

Related Questions