public friend swap member function

There are several ways to write swap, some better than others. Over time, though, it was found a single definition works best. Let’s consider how we might think about writing a swap function.


We first see that containers like std::vector<> have a single-argument member function swap, such as:

struct vector
{
    void swap(vector&) { /* swap members */ }
};

Naturally, then, our class should too, right? Well, not really. The standard library has all sorts of unnecessary things, and a member swap is one of them. Why? Let’s go on.


What we should do is identify what’s canonical, and what our class needs to do to work with it. And the canonical method of swapping is with std::swap. This is why member functions aren’t useful: they aren’t how we should swap things, in general, and have no bearing on the behavior of std::swap.

Well then, to make std::swap work we should provide (and std::vector<> should have provided) a specialization of std::swap, right?

namespace std
{
    template <> // important! specialization in std is OK, overloading is UB
    void swap(myclass&, myclass&)
    {
        // swap
    }
}

Well that would certainly work in this case, but it has a glaring problem: function specializations cannot be partial. That is, we cannot specialize template classes with this, only particular instantiations:

namespace std
{
    template <typename T>
    void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
    {
        // swap
    }
}

This method works some of the time, but not all of the time. There must be a better way.


There is! We can use a friend function, and find it through ADL:

namespace xyz
{
    struct myclass
    {
        friend void swap(myclass&, myclass&);
    };
}

When we want to swap something, we associate std::swap and then make an unqualified call:

using std::swap; // allow use of std::swap...
swap(x, y); // ...but select overloads, first

// that is, if swap(x, y) finds a better match, via ADL, it
// will use that instead; otherwise it falls back to std::swap

What is a friend function? There is confusion around this area.

Before C++ was standardized, friend functions did something called “friend name injection”, where the code behaved as if if the function had been written in the surrounding namespace. For example, these were equivalent pre-standard:

struct foo
{
    friend void bar()
    {
        // baz
    }
};

// turned into, pre-standard:    

struct foo
{
    friend void bar();
};

void bar()
{
    // baz
}

However, when ADL was invented this was removed. The friend function could then only be found via ADL; if you wanted it as a free function, it needed to be declared as so (see this, for example). But lo! There was a problem.

If you just use std::swap(x, y), your overload will never be found, because you’ve explicitly said “look in std, and nowhere else”! This is why some people suggested writing two functions: one as a function to be found via ADL, and the other to handle explicit std:: qualifications.

But like we saw, this can’t work in all cases, and we end up with an ugly mess. Instead, idiomatic swapping went the other route: instead of making it the classes’ job to provide std::swap, it’s the swappers’ job to make sure they don’t use qualified swap, like above. And this tends to work pretty well, as long as people know about it. But therein lies the problem: it’s unintuitive to need to use an unqualified call!

To make this easier, some libraries like Boost provided the function boost::swap, which just does an unqualified call to swap, with std::swap as an associated namespace. This helps make things succinct again, but it’s still a bummer.

Note that there is no change in C++11 to the behavior of std::swap, which I and others mistakenly thought would be the case. If you were bit by this, read here.


In short: the member function is just noise, the specialization is ugly and incomplete, but the friend function is complete and works. And when you swap, either use boost::swap or an unqualified swap with std::swap associated.


†Informally, a name is associated if it will be considered during a function call. For the details, read §3.4.2. In this case, std::swap normally isn’t considered; but we can associate it (add it to the set of overloads considered by unqualified swap), allowing it to be found.

Leave a Comment