Assume the following c++17 code:
#include <type_traits>
namespace dtl
{
struct One
{
explicit One(int);
~One() = default;
One(const One &) = delete;
auto operator=(const One &) -> One & = delete;
auto operator=(One &&) -> One & = delete;
One(One &&); // Throwable, not default;
int m_int;
};
struct Two
{
explicit Two(int);
~Two() = default;
Two(const Two &) = delete;
auto operator=(const Two &) -> Two & = delete;
auto operator=(Two &&) noexcept -> Two & = delete;
Two(Two &&) noexcept = default;
One m_one;
};
One::One(One &&) { throw 1; }
static_assert(std::is_nothrow_move_constructible_v<Two>);
}
Code at compiler-explorer
Here we clearly see that the move constructor of the class One
ain't marked as noexcept. Class Two
has a defaulted move constructor that is explicitly requested to be noexcept.
If we check this code, this compiles with GCC trunk, Clang trunk, MSVC 19.28 and fails with MSVC19.24.
I checked the following sources which seem to tell me that the move constructor of Two
needs to be deleted:
CWG issue 1778 to read (N4296 [dcl.fct.def.default]/p3):
If a function that is explicitly defaulted is declared with an exception-specification that is not compatible (15.4) with the exception specification on 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.
Based on that information, I can only conclude that all 3 compilers are wrong in considering Two
as no_throw_move_constructible and the move constructor should be implicitly deleted. As it's strange for all 3 to ignore the standard on this, I do wonder: Is this really a compiler bug or am I missing something.
Tagging our move constructor with "noexcept" tells the compiler that it will not throw any exceptions. This condition is checked in C++ using the type trait function: "std::is_no_throw_move_constructible". This function will tell you whether the specifier is correctly set on your move constructor.
Inheriting constructors and the implicitly-declared default constructors, copy constructors, move constructors, destructors, copy-assignment operators, move-assignment operators are all noexcept(true) by default, unless they are required to call a function that is noexcept(false) , in which case these functions are ...
For non-union class types (class and struct), the move constructor performs full member-wise move of the object's bases and non-static members, in their initialization order, using direct initialization with an xvalue argument.
No move constructor is automatically generated.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With