32

This question is based on discussion below a recent blog post by Scott Meyers.

It seems "obvious" that std::swap(x, x) should leave x unchanged in both C++98 and C++11, but I can't find any guarantee to that effect in either standard. C++98 defines std::swap in terms of copy construction and copy assignment, while C++11 defines it in terms of move construction and move assignment, and this seems relevant, because in C++11 (and C++14), 17.6.4.9 says that move-assignment need not be self-assignment-safe:

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 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. —end note ]

The defect report that gave rise to this wording makes the consequence clear:

this clarifies that move assignment operators need not perform the traditional if (this != &rhs) test commonly found (and needed) in copy assignment operators.

But in C++11 and C++14, std::swap is expected to use this implementation,

template<typename T>
void swap(T& lhs, T& rhs)
{
  auto temp(std::move(lhs));
  lhs = std::move(rhs);
  rhs = std::move(temp); 
}

and the first assignment is performing an assignment to self where the argument is an rvalue. If the move assignment operator for T follows the policy of the standard library and doesn't worry about assignment to self, this would seem to court undefined behavior, and that would mean that std::swap(x, x) would have UB, as well.

That's worrisome even in isolation, but if we assume that std::swap(x, x) was supposed to be safe in C++98, it also means that C++11/14's std::swap could silently break C++98 code.

So is std::swap(x, x) guaranteed to leave x unchanged? In C++98? In C++11? If it is, how does this interact with 17.6.4.9's permission for move-assignment to not be self-assignment-safe?

KnowItAllWannabe
  • 12,282
  • 7
  • 45
  • 86
  • 1. Why do you believe that swap must be implemented using move? I do not see that in the standard. 2. I don't see the assignment to self in your swap implementation!? – Werner Henze Jun 27 '14 at 08:18
  • @WernerHenze: 1. Per 20.2.2 of the Standard, the requirement for swappable types is that they must be MoveConstructible and MoveAssignable. 2. In the call `std::swap(x, x)`, the parameters `lhs` and `rhs` are the same object, so `lhs = std::move(rhs)` is an assignment to self. – KnowItAllWannabe Jun 27 '14 at 16:40
  • You are right about 2., I missed that. Regarding 1.: the standard does not give an implementation, so I do not think that this is a defect in the standard but in the implementation. The implementation could check if &lhs==&rhs and then skip the move. – Werner Henze Jun 30 '14 at 10:00
  • DR1204 which you linked to is talking about move-assignment operators for standard library implementations. That doesn't apply as a general statement to user-defined classes. – M.M Mar 29 '15 at 00:42
  • BTW, it does not matter when self-move-assignment damages the value in your swap implementation. After the self-assignment, there is a move-assignment to not-self which resurrects the original value, provided that such not-self-assignments do not damage the value. – j6t May 21 '21 at 09:24

1 Answers1

4

I believe this may be covered, at least in C++20(a), by the [utility.requirements] section which states:

15.5.3.2 describes the requirements on swappable types and swappable expressions.

That referenced section, [swappable.requirements], further states (my emphasis in the final two bullet points):

An object t is swappable with an object u if and only if:

  • the expressions swap(t, u) and swap(u, t) are valid when evaluated in the context described below; and
  • these expressions have the following effects:
    • the object referred to by t has the value originally held by u; and
    • the object referred to by u has the value originally held by t.

It seems to me that, if self-swap somehow damaged the contents, those bolded sections would be invalidated, meaning that they wouldn't be swappable.

That same section also later states:

An rvalue or lvalue t is swappable if and only if t is swappable with any rvalue or lvalue, respectively, of type T.

There's no waffling around with self-swaps there, it clearly states any rvalue or lvalue (respectively), including itself.


(a) Both these constraints also exist in C++17, c++14, and C++11, anything older than that, I don't really care about :-)

paxdiablo
  • 814,905
  • 225
  • 1,535
  • 1,899