What is is_nothrow_copy_assignable value for a class with a metal copy constructor and noexcept copy assignment by value?

What is, from the point of view 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::is_nothrow_move_assignable<A>::value << "\n"; } 

In other words, does the attribute value only evaluate the declaration of the assignment operator, which is no exception, and thus it gives

 true true 

Or it considers the call 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 he give

 false true 

Practical attempts

Running code with Visual Studio 2015, Update 3 gives

 true true 

whereas gcc 6.1 gives

 false true 

Who is right?

Background

A similar situation occurs when we have a resource management class using the metadata of the copy constructor (since resource allocation may fail), the express-free constructor, metadata assignment, and non-capturing assignment.

Suppose that both copying and moving can be effectively implemented from the point of view of idom exchange:

 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 could consider the possibility of condensation as a single copy assignment by default

 A& operator=(A other) noexcept { other.swap(*this); return *this; } 

However, we can only do this safely if std::is_nothrow_copy_assignable<A> and std::is_nothrow_move_assignable<A> provide the correct values ​​(false and true, respectively). Otherwise, code based on these types will behave badly, and our single value assignment will not be a suitable replacement for two separate assignment operators.

+5
source share
1 answer

The definition of is_nothrow_copy_assignable is in [meta.unary.prop]:

For a reference type T , the same result as is_nothrow_assignable_v<T&, const T&> , otherwise false .

Well, A is referential (meaning A& ). So, go to is_nothrow_assignable :

is_assignable_v<T, U> true , and the destination, as you know, should not cause any exceptions (5.3.7).

is_assignable_v<A, A const&> definitely true , so we satisfy the first part. What does it mean to be famous so as not to throw any exceptions? According to [expr.unary.noexcept]:

The noexcept operator determines whether evaluating its operand, which is an unvalued operand (clause 5), can eliminate the exception (15.1). [...] The result of the noexcept statement noexcept true if the set of potential exceptions of expression (15.4) is empty, and false otherwise.

And in [except.spec]:

The exception specification noexcept or noexcept(constant-expression) , where the constant expression gives true , denotes the exception specification, which is an empty set. The exception specification is noexcept(constant-expression) , where the constant expression gives false or no exception specification in the function declarator, other than for the destructor (12.4) or the release function (3.7.4.2) it means the exception specification, which is a set of all types.

and

The set of potential exceptions of the expression e is empty if e is the main constant expression (5.20). Otherwise, it is the union of the sets of potential exceptions from the direct subexpressions of e, including the default of the arguments used in the function call, in combination with the set S defined by the form e, as follows: [...]
- If e implicitly calls one or more functions (for example, an overloaded operator, a distribution function in a new expression, or a destructor if e is the full expression (1.9)), S is a union: - a set of types in the exception specifications for all such functions and
- if e is a new expression [...]

Now assigning A from A const& involves two steps:

  • call copy constructor A
  • call copy assignment operator A

An exception specification is the union of all the exception specifications of both of these functions, which is a collection of all types because the copy constructor does not have an exception specification at all.

Therefore, is_nothrow_copy_assignable_v<A> must be false . gcc is correct.

+7
source

Source: https://habr.com/ru/post/1263808/


All Articles