C++ the good parts — std::swap


std::swap is quite fundamental. And allow me to assume, fundamentally glanced over by you :) Which is a pity; understanding std::swap concept fully, will make you more clear on a few other important C++ concepts.

Advice: to understand C++ swap one must be sure of the other C++ fundamentals fully. Please, if in doubt, stop reading and jump to the link provided in the text in bold font, for a quick refresh and then jump back.


The easy part

Two easy things to mention before we dive in.

Canonical swap implementation on your class

Here is how you provide your own swap for your class/struct :

template<typename Data_type>
struct my_struct {
  // this position will maked sure ADL will find it
  friend void swap( my_struct  & a, my_struct  & b) {
   // std::swap is the default or fallback
    using std::swap;
    swap(a.data, b.data);
  Data_type n;

And this is how it will be called/used:

void main() {
  // std::swap is the default or fallback
  using std::swap; 
  my_struct <int> a { 42 } , b { 13 };
  swap(a, b); // ADL invocation
  assert( a.n == 13 );

If not sure, look up ADL. Now let us proceed.

Swap is not Exchange

Exchange of values is the other fundamental and distinctive operation.  The language you code in must allow you to clearly separate the fundamental operations.  The key difference between swap and exchange is the lifetime of values swapped or exchanged.

Swap ( A , B )  // B life is over, it is a zombie. Do not deal with zombies

Exchange (A, B) // both B and A are alive and well, content exchanged

Caveat Emptor: here we use the word “exchange” as normal people do. In case you know or do not want to know about std::exchange please see here.

Most importantly: B is said to be moved from after swap is done. (hint: yup, here is that link to the refresher, in bold ). So.

Let’s Move

The move is a fundamental building block. The synopsis std::swap is:

template < typename T>
void std::swap ( T & a, T & b )
   // here the move ctor on temp is called
   T temp = std::move( a ) ;
   // the move assignment is used here
     a = std::move( b );
   // the move assignment is used here
     b = std::move( temp );

That is a generic function. The use of std::move assures first, the move is conceptually implemented and executed and second, values are moved for all the types involved for arguments a and b above.

The swap concept is using the move concept. Move concept is the fundamental building block to value semantics of the to modern C++.  The move is fundamental in making value semantics feasible. With the addition of “move constructor” and “move assignment”.

To understand and use for your own benefit the moving and swapping in C++ please always be aware of what type category, exactly is being moved or swapped. We will focus on the differences regarding the types of values moved or swapped.

In here we will peruse three particular categories of types to form the facets of the C++ moving/swapping. We will name them into three groups for this post:

  1. Fundamental Types
  2. User-Defined Trivial Types
  3. User-Defined Non-Trivial Types

There is a bit more to this whole subject. I might suggest you to start from here. Before proceeding please be sure you are clear on the C++ type system.

Swapping fundamental types

This type category is here as a natural and good starting point on the std::swap journey of discovery.  There is nothing surprising about swapping fundamental types.

// no surprises is a good news
int A = 2, B = 13 ;
std::swap(A,B) ;
// A is 13 and B is 2

The most important thing to notice here:  B is moved from. C++ standard does mention that explicitly: Bi is in the “undefined state” after the move of its value to A.  Remember: Swap is not Exchange.

You obviously can see it, B is actually alive and well, and equals 2, as you have expected. But keep in mind C++ is a language based on abstract thinking and abstractions. Thus the swap definition is carefully made so it applies to all the types of A and B above.  Above just happens to be two integers. But please always have this mental picture

// T can be any complex type
// it happens in here it is constructible 
// with number literals
T A = 2, B = 13 ; 
// it also happens to be swappable 
// with the inbuilt std::swap
std::swap(A,B) ; 
// A is 13 and B is "undefined"!

That is actually standardized.  It is very precisely defined what you can swap and what you can not swap.   Any type T can be swapped as long as it is Swappable , which in code can be (and it is) checked with std::is_swappable and friends.

And crucially there is always one left in the “undefined state” because it was “moved from” inside a swap operation. Now onto the next group of types.