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-idT
if and only ifT
is allowed by the exception-specification
of a function directly invoked byf
’s implicit definition;f
allows all exceptions if any function it directly invokes allows all exceptions, andf
has the exception-specificationnoexcept(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 declaredconstexpr
only if it would have been implicitly declared
asconstexpr
. 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 default
ed 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
C++ Move Semantics and Exceptions
Is Returning with 'Std::Move' Sensible in the Case of Multiple Return Statements
How to Force Inclusion of an Object File in a Static Library When Linking into Executable
Check If a Type Is from a Particular Namespace
Opinions on Type-Punning in C++
C++11 Emplace_Back on Vector<Struct>
Delete All Items from a C++ Std::Vector
How to Get Hash Code of a String in C++
Is There a Downside to Declaring Variables with Auto in C++
Serial Port (Rs -232) Connection in C++
How to Write a Stateful Allocator in C++11, Given Requirements on Copy Construction
Qt/Qml:Send Qimage from C++ to Qml and Display the Qimage on Gui
Ad Hoc Polymorphism and Heterogeneous Containers with Value Semantics
How to Use C++ Std::Ostream with Printf-Like Formatting
What's the Best Way to Check If a File Exists in C++? (Cross Platform)