Reputation: 56577
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
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 beconstexpr
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-idT
if and only ifT
is allowed by the exception-specification of a function directly invoked byf
's implicit definition;f
shall allow all exceptions if any function it directly invokes allows all exceptions, andf
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
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