What is, in terms of the C++ standard, the expected (if any) output of the following program:
#include <iostream>
#include <iomanip>
#include <type_traits>
class A {
public:
A() = default;
~A() = default;
A(A const& other) {}
A(A&& other) noexcept {}
A& operator=(A other) noexcept { return *this; }
};
int main() {
std::cout
<< std::boolalpha << std::is_nothrow_copy_assignable<A>::value << "\n"
<< std::boolalpha << std::is_nothrow_move_assignable<A>::value << "\n";
}
In other words, does the evaluation of the type traits' values look at the declaration of the assignment operator only, which is noexcept, and does it thus yield
true
true
Or does it consider the calling context (a, b are instances of A)
a = b; // may throw, implicitly calls copy c'tor
a = std::move(b); // noexcept, implicitly calls move c'tor
and does it yield
false
true
Practical attempts
Running the code with Visual Studio 2015, Update 3 gives
true
true
whereas gcc 6.1 gives
false
true
Who is right?
Background
A situation like this occurs when we have a resource managing class with a throwing copy constructor (since resource allocation may fail), a noexcept move constructor, a throwing copy assignment and a noexcept move assignment.
Supposing that both copy and move assignment can be efficiently implemented in terms of the swap idom:
A& operator=(A const& other) {
A(other).swap(*this); // calls the copy c'tor, may throw
return *this;
}
A& operator=(A&& other) noexcept {
A(std::move(other)).swap(*this); // calls noexcept move c'tor
return *this;
}
Then we might consider condensing both into the single by-value copy assignment
A& operator=(A other) noexcept {
other.swap(*this);
return *this;
}
However, we can only safely do this if we can rely on std::is_nothrow_copy_assignable<A> and std::is_nothrow_move_assignable<A> providing the correct values (false and true, respectively). Otherwise, our class will behave badly, for example when used as the value type of a std::vector.
Aucun commentaire:
Enregistrer un commentaire