Is the Default Move Constructor Defined as Noexcept

Is the default Move constructor defined as noexcept?

I think the answer is 15.4/14 (Exception specifications):

An inheriting constructor (12.9) and an implicitly declared special member function (Clause 12) have an exception-specification. If f is an inheriting constructor or 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 allows all exceptions if any function it directly invokes allows all exceptions, and f has the exception-specification noexcept(true) if every function it directly invokes allows no exceptions.

Basically, it Does What You Think, and the implicitly-declared move constructor is noexcept whenever it can be.

Why is default noexcept move constructor being accepted?

I believe that you are looking at outdated information. DR1778 has been superceded by P1286R2. If you look at the implementation status, you will see that gcc 10 and clang 9 implement this new resolution.

Indeed, if you go back to older gcc versions in godbolt, it tells you:

<source>: In function 'int main()':
<source>:35:25: error: use of deleted function 'dtl::Two::Two(dtl::Two&&)'
35 | auto b = std::move(a);
| ^
<source>:23:7: note: 'dtl::Two::Two(dtl::Two&&) noexcept' is implicitly deleted because its exception-specification does not match the implicit exception-specification ''
23 | Two(Two &&) noexcept = default;
| ^~~
Compiler returned: 1

You can find the gcc discussion here. According to this list, P1286R2 was accepted as a DR, meaning that it was retroactively applied to previous standards. As such, newer compilers will behave in the way that you noticed, independent of the chosen C++ standard.

At runtime, however, this will fail as expected:

dtl::One::One(int) {};
dtl::Two::Two(int) : m_one(0) {};

int main() {
auto a = dtl::Two{1};
try {
auto b = std::move(a);
} catch (...) {
// Even though an exception is thrown, it will not be caught here because
// we broke our `noexcept` promise.
std::cout << "caught" << std::endl;
}
return 0;
}
[:~/tmp] $ /usr/local/Cellar/llvm/11.0.0/bin/clang++ -std=c++17 mv.cpp  && ./a.out
libc++abi.dylib: terminating with uncaught exception of type int
Abort trap: 6

Implicit move constructor shall be noexcept if possible

Is there something I am missing?

Yes. const

The member Foo::stringField is not std::string, it is const std::string. const std::string is not nothrow move constructible1, and therefore the implicit move constructor of Foo is neither.

1 Const rvalue cannot bind to a non-const rvalue reference, so move constructor will not be used. Instead, copy constructor is used and copy constructor of std::string is potentially throwing.

What are the rules for noexcept on default defined move constructors?

[dcl.fct.def.default]/p2:

If a function is explicitly defaulted on its first declaration,

  • it is implicitly considered to be constexpr if the implicit declaration would be, and,
  • it has the same exception specification as if it had been implicitly declared (15.4).

These rules do not apply if the function is explicitly defaulted on a later declaration, as in your later example, so instead, except for destructors, the function is considered noexcept(false) by default like most other functions.

Since the explicit defaulting can be in a different translation unit - and in the pimpl case, is in a different TU - there's no general way for the compiler to figure out after seeing the class definition only whether the move constructor will throw, unless the function is explicitly defaulted in the class definition (i.e., at its first declaration).

Why is my defaulted move constructor not noexcept?

In fact it has nothing to do with noexcept; static_assert would fail also with std::is_move_constructible because the move constructor is private. So just declare it as public.

class D {
public:
D(D&&) = default;
};

LIVE with Clang8

Is a defaulted constructor/assignment noexcept/constexpr by default?

[dcl.fct.def.default]/2-3:

2 An explicitly-defaulted function that is not defined as deleted may
be declared constexpr only if it would have been implicitly declared
as constexpr. If a function is explicitly defaulted on its first
declaration,

  • it is implicitly considered to be constexpr if the implicit declaration would be, and,
  • it has the same exception specification as if it had been implicitly declared ([except.spec]).

3 If a function that is explicitly defaulted is declared with an
exception-specification that is not compatible ([except.spec]) with
the exception specification of the implicit declaration, then

  • if the function is explicitly defaulted on its first declaration, it is defined as deleted;

  • otherwise, the program is ill-formed.

In other words, foo() = default;, which is necessarily the first declaration of foo's default constructor, will be "constexpr if possible" and "noexcept if possible". Explicitly writing constexpr and noexcept is still useful; it means "yell at me if it can't be constexpr/noexcept".

How to reference move constructor in noexcept operator

The operator noexcept() needs a true expression. Unfortunately T(T&&) is not a valid expression.

So you'd need to instantiate an occurrence of T and use std::move() to make sure it uses the move constructor if there's one. Here a proof of concept:

template <typename T> 
struct TypeInfo
{
bool test()
{
T t;
bool IsNothrowMoveConstructible = noexcept(T(std::move(t)));
return IsNothrowMoveConstructible;
};
};

The problem is that this becomes much more error prone. If T has no default constructor, it will fail to compile. Same if move constructor was implicitly or explicitly deleted.

But if you can live with these flaws, as the value is determined at compile time and is therefore constant, you could use a T member and define a constant in an enum:

struct TypeInfo 
{
T t;
enum {
IsNothrowMoveConstructible = noexcept(T(std::move(t)))
};
};

Here an online demo.

What does explicitly-defaulted move constructor do?

What does explicitly-defaulted move constructor do?

It will initialize its member variables with the corresponding member variables in the moved from object after turning them into xvalues. This is the same as if you use std::move in your own implementation:

Base(Base&& other) noexcept : id(std::move(other.id)) {}

Note though that for fundamental types, like int, this is no different from copying the value. If you want the moved from object to have its id set to 0, use std::exchange:

Base(Base&& other) noexcept : id(std::exchange(other.id, 0)) {}

Base& operator=(Base&& other) noexcept {
if(this != &other) id = std::exchange(other.id, 0);
return *this;
}

With that, the Foo move constructor and move assignment operator can be defaulted and "do the right thing".


Suggestion: I think Base would benefit from a constructor that can initialize id directly. Example:

Base(int Id) : id(Id) {
std::cout << "Base() called " << id << std::endl;
}

Base() : Base(0) {} // delegate to the above

Now, the Foo constructor taking an id as an argument could look like this:

Foo(int Id) : Base(Id) {
std::cout << "Foo() called " << id << std::endl;
}


Related Topics



Leave a reply



Submit