Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

On implementing std::swap in terms of move assignment and move constructor

Here is a possible definition of std::swap:

template<class T>
void swap(T& a, T& b) {
  T tmp(std::move(a));
  a = std::move(b);
  b = std::move(tmp);
}

I believe that

  1. std::swap(v,v) is guaranteed to have no effects and
  2. std::swap can be implemented as above.

The following quote seems to me to imply that these beliefs are contradictory.

17.6.4.9 Function arguments [res.on.arguments]

1 Each of the following applies to all arguments to functions defined in the C++ standard library, unless explicitly stated otherwise.

...

  • If a function argument binds to an rvalue reference parameter, the implementation may assume that this parameter is a unique reference to this argument. [ Note: If the parameter is a generic parameter of the form T&& and an lvalue of type A is bound, the argument binds to an lvalue reference (14.8.2.1) and thus is not covered by the previous sentence. — end note ] [ Note: If a program casts an lvalue to an xvalue while passing that lvalue to a library function (e.g. by calling the function with the argument move(x)), the program is effectively asking that function to treat that lvalue as a temporary. The implementation is free to optimize away aliasing checks which might be needed if the argument was an lvalue. —endnote]

(thanks to Howard Hinnant for providing the quote)

Let v be an object of some movable type taken from the Standard Template Library and consider the call std::swap(v, v). In the line a = std::move(b); above, it is the case inside T::operator=(T&& t) that this == &b, so the parameter is not a unique reference. That is a violation of the requirement made above, so the line a = std::move(b) invokes undefined behavior when called from std::swap(v, v).

What is the explanation here?

like image 277
Bjarke H. Roune Avatar asked Oct 29 '12 20:10

Bjarke H. Roune


2 Answers

[res.on.arguments] is a statement about how the client should use the std::lib. When the client sends an xvalue to a std::lib function, the client has to be willing to pretend that the xvalue is really a prvalue, and expect the std::lib to take advantage of that.

However when the client calls std::swap(x, x), the client isn't sending an xvalue to a std::lib function. It is the implementation that is doing so instead. And so the onus is on the implementation to make std::swap(x, x) work.

That being said, the std has given the implementor a guarantee: X shall satisfy MoveAssignable. Even if in a moved-from state, the client must ensure that X is MoveAssignable. Furthermore, the implementation of std::swap doesn't really care what self-move-assignment does, as long as it is not undefined behavior for X. I.e. as long as it doesn't crash.

a = std::move(b);

When &a == &b, both the source and target of this assignment have an unspecified (moved-from) value. This can be a no-op, or it can do something else. As long as it doesn't crash, std::swap will work correctly. This is because in the next line:

b = std::move(tmp);

Whatever value went into a from the previous line is going to be given a new value from tmp. And tmp has the original value of a. So besides burning up a lot of cpu cycles, swap(a, a) is a no-op.

Update

The latest working draft, N4618 has been modified to clearly state that in the MoveAssignable requirements the expression:

t = rv

(where rv is an rvalue), t need only be the equivalent value of rv prior to the assignment if t and rv do not reference the same object. And regardless, rv's state is unspecified after the assignment. There is an additional note for further clarification:

rv must still meet the requirements of the library component that is using it, whether or not t and rv refer to the same object.

like image 152
Howard Hinnant Avatar answered Sep 29 '22 06:09

Howard Hinnant


Then the expression a = std::move(b); gets executed, the object is already empty, in a state where only destruction is well defined. That will effectively be a no-op, as the object on the left and right hand sides is already empty. The state of the object after the move is still unknown but destructible. The next statement moves the contents back from tmp and that sets the object back to a known state.

like image 38
David Rodríguez - dribeas Avatar answered Sep 29 '22 08:09

David Rodríguez - dribeas