44

While looking at the documentation for std::swap, I see a lot of specializations.
It looks like every STL container, as well as many other std facilities have a specialized swap.
I thought with the aid of templates, we wouldn't need all of these specializations?

For example,
If I write my own pair it works correctly with the templated version:

template<class T1,class T2> 
struct my_pair{
    T1 t1;
    T2 t2;
};

int main() {
    my_pair<int,char> x{1,'a'};
    my_pair<int,char> y{2,'b'};
    std::swap(x,y);
} 

So what it is gained from specializing std::pair?

template< class T1, class T2 >
void swap( pair<T1,T2>& lhs, pair<T1,T2>& rhs );

I'm also left wondering if I should be writing my own specializations for custom classes,
or simply relying on the template version.

Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
  • 7
    The `swap()` methods implemented on the containers can take advantage of the containers internal structure and therefore be more efficient (eg `std::list` just swap the pointers and size). `std::swap` can be used in template(s) when you don't know the container type. The specialisations of `std::swap` can call the container methods to take advantage of the more efficient emplementation – Richard Critten Feb 01 '17 at 16:27
  • 2
    I suspect the difference was much bigger before move semantics. Before that access to the container internals meant just switching pointers, while the generic route (without special compiler support) would need to do potentially a lot of copying. – PeterSW Feb 01 '17 at 16:33
  • 12
    They are overloads, not template specializations. – Oktalist Feb 01 '17 at 16:38
  • 5
    Swapping a pair can be a pain. The first thing you need to do is get the dang partridge out of the tree.... – user4581301 Feb 01 '17 at 17:00
  • 1
    Most times I find myself writing a `swap` overload, I'm doing it because I want to make use of the [“copy & swap” idiom](https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom). – 5gon12eder Feb 01 '17 at 23:41
  • 1
    @5gon12eder typically that is a member swap function, not an overload of `std::swap` – M.M Feb 02 '17 at 02:26

6 Answers6

38

So what it is gained from specializing std::pair?

Performance. The generic swap is usually good enough (since C++11), but rarely optimal (for std::pair, and for most other data structures).

I'm also left wondering if I should be writing my own specializations for custom classes, or simply relying on the template version.

I suggest relying on the template by default, but if profiling shows it to be a bottleneck, know that there is probably room for improvement. Premature optimization and all that...

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Did I miss the actual content of the answer? _Why_ is the generic implementation "rarely optimal"? _What_ can specializations do to improve on that? – underscore_d Jun 09 '23 at 21:57
35

std::swap is implemented along the lines of the code below:

template<typename T> void swap(T& t1, T& t2) {
    T temp = std::move(t1); 
    t1 = std::move(t2);
    t2 = std::move(temp);
}

(See "How does the standard library implement std::swap?" for more information.)

So what it is gained from specializing std::pair?

std::swap can be specialized in the following way (simplified from libc++):

void swap(pair& p) noexcept(is_nothrow_swappable<first_type>{} &&
                            is_nothrow_swappable<second_type>{})
{
    using std::swap;
    swap(first,  p.first);
    swap(second, p.second);
}

As you can see, swap is directly invoked on the elements of the pair using ADL: this allows customized and potentially faster implementations of swap to be used on first and second (those implementations can exploit the knowledge of the internal structure of the elements for more performance).

(See "How does using std::swap enable ADL?" for more information.)

Community
  • 1
  • 1
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • This does not answer OP's question completely. The generic implementation you suggested should perfectly take advantage of cheaply `moving` internal structures (pointers and such) of STL containers via move constructors without moving every single element. The answer relies on pre-c++11 where move semtantics did not exist. And there is no reason to remove the specializations braking the 100% backward compatibility which some weird code may rely on. – Etherealone Feb 07 '17 at 21:57
  • @Etherealone: I do not understand your argument, especially the part where you say that I rely on "pre-c++11". While the generic implementation is definitely very efficient due to move semantics, it might not be as optimal as the presented `std::pair` implementation taken from libc++. Also what do you mean by "remove the specializations"? – Vittorio Romeo Feb 08 '17 at 09:41
  • 2
    Generic implementation had to copy before c++11/move semantics and specializations could allow containers to copy/swap internal structure pointers instead of whole container with contained objects. But after introducing move semantics, isn't moving the containers via the generic swap implementation (using their move constructors) just the same as specializing swap to move the internal structure pointers? Is there a case where specialization of swap will have more advantage over the generic swap while using on well-defined objects (e.g. objects with efficient move constructors etc.)? – Etherealone Feb 08 '17 at 12:13
  • 1
    After all, I think using the generic swap on a `pair` with move constructor would simply result in similar code to the specialization you provided. Such as `pair::pair(pair&& rhs) : first{std::move(rhs.first)}, ...` and the specialization you provided does the same via `swap(first, p.first);`. – Etherealone Feb 08 '17 at 12:17
13

Presumably this is for performance reasons in the case that the pair's contained types are cheap to swap but expensive to copy, like vector. Since it can call swap on first and second instead of doing a copy with temporary objects it may provide a significant improvement to program performance.

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • Thanks for the concrete example. – Mark Ransom Feb 01 '17 at 16:31
  • 1
    The explicit element swap could also be *slower* than the temp-variable approach. Take for example `class Foo { char a, b, c, d, e, f, g, h; };` (ok, insane type, but bear with me). This class has a default copy constructor and an empty destructor. The default copy constructor is equivalent to `memcpy(dest, src, sizeof(Foo));` which will compile to a *single 64 bit load and store*. The temporary variable swap would thus compile to three or six instructions. The element based swap would likely require 24 or 48 instructions (or a loop with eight iterations). – cmaster - reinstate monica Feb 01 '17 at 21:50
6

The reason is performance, especially pre c++11.

Consider something like a "Vector" type. The Vector has three fields: size, capacity and a pointer to the actual data. It's copy constructor and copy assignment copy the actual data. The C++11 version also has a move constructor and move assignment that steal the pointer, setting the pointer in the source object to null.

A dedicated Vector swap implementation can simply swap the fields.

A generic swap implementation based on the copy constructor, copy assignment and destructor will result in data copying and dynamic memory allocation/deallocation.

A generic swap implementation based on the move constructor, move assignment and destructor will avoid any data copying or memory allocation but it will leave some redundant nulling and null-checks which the optimiser may or may not be able to optimise away.

This means that post c++11 the generic swap is, depending on how good the optimiser is, perhaps slightly worse than a specific one but likely still pretty cheap. On the other hand pre c++11 a generic swap implementation was massively worse than a specific one.


So why have a specialised swap implementation for "Pair"? For a pair of int and char there is no need. They are plain old data types so a generic swap is just fine.

But what if I have a pair of say Vector and String ? I want to use the specialist swap operations for those types and so I need a swap operation on the pair type that handles it by swapping it's component elements.

plugwash
  • 9,724
  • 2
  • 38
  • 51
5

The most efficient way to swap two pairs is not the same as the most efficient way to swap two vectors. The two types have a different implementation, different member variables and different member functions.

There is no just generic way to "swap" two objects in this manner.

I mean, sure, for a copyable type you could do this:

T tmp = a;
a = b;
b = tmp;

But that's horrendous.

For a moveable type you can add some std::move and prevent copies, but then you still need "swap" semantics at the next layer down in order to actually have useful move semantics. At some point, you need to specialise.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
2

There is a rule (I think it comes from either Herb Sutter's Exceptional C++ or Scott Meyer's Effective C++ series) that if your type can provide a swap implementation that does not throw, or is faster than the generic std::swap function, it should do so as member function void swap(T &other).

Theoretically, the generic std::swap() function could use template magic to detect the presence of a member-swap and call that instead of doing

T tmp = std::move(lhs);
lhs = std::move(rhs);
rhs = std::move(tmp);

but no-one seems to have thought about that one, yet, so people tend to add overloads of free swap in order to call the (potentially faster) member-swap.

Barmar
  • 741,623
  • 53
  • 500
  • 612
Marc Mutz - mmutz
  • 24,485
  • 12
  • 80
  • 90