How to overload std::swap()
Asked Answered
N

4

128

std::swap() is used by many std containers (such as std::list and std::vector) during sorting and even assignment.

But the std implementation of swap() is very generalized and rather inefficient for custom types.

Thus efficiency can be gained by overloading std::swap() with a custom type specific implementation. But how can you implement it so it will be used by the std containers?

Nide answered 14/8, 2008 at 19:24 Comment(1)
The Swappable page moved to en.cppreference.com/w/cpp/named_req/SwappablePamalapamela
I
154

The right way to overload std::swap's implemention (aka specializing it), is to write it in the same namespace as what you're swapping, so that it can be found via argument-dependent lookup (ADL). One particularly easy thing to do is:

class X
{
    // ...
    friend void swap(X& a, X& b)
    {
        using std::swap; // bring in swap for built-in types

        swap(a.base1, b.base1);
        swap(a.base2, b.base2);
        // ...
        swap(a.member1, b.member1);
        swap(a.member2, b.member2);
        // ...
    }
};
Idoux answered 21/4, 2010 at 16:2 Comment(17)
That's the cleanest way to provide a user defined swap, but it doesn't help in the scenario the questioner is asking about, as std::sort doesn't use ADL to find swap.Atrophy
In C++2003 it's at best underspecified. Most implementations do use ADL to find swap, but no it's not mandated, so you can't count on it. You can specialize std::swap for a specific concrete type as shown by the OP; just don't expect that specialization to get used, e.g. for derived classes of that type.Idoux
I would be surprised to find that implementations still don't use ADL to find the correct swap. This is an old issue on the committee. If your implementation doesn't use ADL to find swap, file a bug report.Parrisch
Somebody should check the library that ships with MSVC. I know they were a holdout for a long time on this one.Idoux
I don't think this is a good idea. First of all: Why defining this function at global scope only? There's no reason why there shouldn't be a member function, too. Secondly, this code means that we have two implementations of swap for 'X'. There is the default one from std namespace and this one.Lapin
@Sascha: First, I'm defining the function at namespace scope because that's the only kind of definition that matters to generic code. Because int et. al. don't/can't have member functions, std::sort et. al. have to use a free function; they establish the protocol. Second, I don't know why you object to having two implementations, but most classes are doomed to being sorted inefficiently if you can't accept having a non-member swap. Overloading rules ensure that if both declarations are seen, the more specific one (this one) will be chosen when swap is called without qualification.Idoux
@Howard Hinnant: If your standard library implementation uses ADL to find a function called swap, then it's broken. An implementation of std::sort must meet the requirements set out in the standard regardless of whether a function called swap exists in a user defined namespace (but if a user specializes std::swap then that specialization must meet the requirements of std::swap otherwise they invoke undefined behaviour).Atrophy
Above comment refers to C++98 and C++03 - C++11 provides requirements for swappable types in section 17.6.3.2.Atrophy
@JoeGauterin: Just tested this with gcc, its std::sort used a swap method defined in this way. Does that mean gcc is non-compliant on this point?Rey
-1 because a client of my class Foo who wants to use swap is likely to use std::swap(foo1, foo2) which will silently use the inefficient default method.Rey
@Mozza314: It depends. A std::sort that uses ADL to swap elements is non-conforming C++03 but conforming C++11. Also, why -1 an answer based on the fact that clients might use non-idiomatic code?Atrophy
Well I used it on gcc 4.4 without -std=c++0x, so I guess it's non-compliant. I find that strange though, since gcc is highly conforming and I didn't know of any case other than the exemption of export. Also, I'm curious about how idiomatic using std::swap; swap(foo1, foo2); is since it is out-of-step with the vast majority of code that uses std::cout, std::pow etc. and so this quirk would only be known to those who have specifically researched it. I'm a trainee C++ developer and I didn't know it, and I just did a straw poll of a couple of my colleagues and they didn't know it either.Rey
@Mozza314: I wouldn't call it non-compliant. Since it was underspecified, using ADL here is a conforming extension. Not sure what to say about your curiosity; that's the right idiom to use unless you want to grab a nice wrapper like boost.org/doc/libs/release/libs/utility/swap.html, which can be called with qualification (boost::swap(x,y)) and will use ADL internally.Idoux
@DaveAbrahams How what this ever "underspecified"? The standard just never allowed it! What is not allowed is forbidden.Electronic
@curiousguy: If reading the standard was just a simple matter of reading the standard, you’d be right :-). Unfortunately, the intent of the authors matters. So if the original intent was that ADL could or should be used, it’s underspecified. If not, then it’s just a plain old breaking change for C++0x, which is why I wrote “at best” underspecified.Idoux
@DaveAbrahams How do you know the intent of people? Do you have a source? (the script of a debate, a discussion thread, a resolution, a design paper can indicate intent)Electronic
@Electronic Yes, he does have a source! He is Dave AbrahamsYentai
P
81

Attention Mozza314

Here is a simulation of the effects of a generic std::algorithm calling std::swap, and having the user provide their swap in namespace std. As this is an experiment, this simulation uses namespace exp instead of namespace std.

// simulate <algorithm>

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap\n");
        T tmp = x;
        x = y;
        y = tmp;
    }

    template <class T>
    void algorithm(T* begin, T* end)
    {
        if (end-begin >= 2)
            exp::swap(begin[0], begin[1]);
    }

}

// simulate user code which includes <algorithm>

struct A
{
};

namespace exp
{
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}

// exercise simulation

int main()
{
    A a[2];
    exp::algorithm(a, a+2);
}

For me this prints out:

generic exp::swap

If your compiler prints out something different then it is not correctly implementing "two-phase lookup" for templates.

If your compiler is conforming (to any of C++98/03/11), then it will give the same output I show. And in that case exactly what you fear will happen, does happen. And putting your swap into namespace std (exp) did not stop it from happening.

Dave and I are both committee members and have been working this area of the standard for a decade (and not always in agreement with each other). But this issue has been settled for a long time, and we both agree on how it has been settled. Disregard Dave's expert opinion/answer in this area at your own peril.

This issue came to light after C++98 was published. Starting about 2001 Dave and I began to work this area. And this is the modern solution:

// simulate <algorithm>

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap\n");
        T tmp = x;
        x = y;
        y = tmp;
    }

    template <class T>
    void algorithm(T* begin, T* end)
    {
        if (end-begin >= 2)
            swap(begin[0], begin[1]);
    }

}

// simulate user code which includes <algorithm>

struct A
{
};

void swap(A&, A&)
{
    printf("swap(A, A)\n");
}

// exercise simulation

int main()
{
    A a[2];
    exp::algorithm(a, a+2);
}

Output is:

swap(A, A)

Update

An observation has been made that:

namespace exp
{    
    template <>
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}

works! So why not use that?

Consider the case that your A is a class template:

// simulate user code which includes <algorithm>

template <class T>
struct A
{
};

namespace exp
{

    template <class T>
    void swap(A<T>&, A<T>&)
    {
        printf("exp::swap(A, A)\n");
    }

}

// exercise simulation

int main()
{
    A<int> a[2];
    exp::algorithm(a, a+2);
}

Now it doesn't work again. :-(

So you could put swap in namespace std and have it work. But you'll need to remember to put swap in A's namespace for the case when you have a template: A<T>. And since both cases will work if you put swap in A's namespace, it is just easier to remember (and to teach others) to just do it that one way.

Parrisch answered 8/12, 2011 at 23:52 Comment(19)
Thankyou very much for the detailed answer. I am clearly less knowledgeable about this and was actually wondering how overloading and specialisation could produce different behaviour. However, I'm not suggesting overloading but specialisation. When I put template <> in your first example I get output exp::swap(A, A) from gcc. So, why not prefer specialisation?Rey
Wow! This is really enlightening. You have definitely convinced me. I think I will slightly modify your suggestion and use the in-class friend syntax from Dave Abrahams (hey I can use this for operator<< too! :-) ), unless you have a reason to avoid that as well (other than compiling separately). Also, in light of this, do you think using std::swap is an exception to the "never put using statements inside header files" rule? In fact, why not put using std::swap inside <algorithm>? I suppose it could break a tiny minority of people's code. Maybe deprecate support and eventually put it in?Rey
in-class friend syntax should be fine. I would try to limit using std::swap to function scope within your headers. Yes, swap is almost a keyword. But no, it is not quite a keyword. So best not to export it to all namespaces until you really have to. swap is much like operator==. The biggest difference is that no ever even thinks of calling operator== with qualified namespace syntax (it would just be too ugly).Parrisch
But if exporting swap to all namespaces causes something to break, that something was bad practice, exposing it so it can be fixed. That's a good thing right?Rey
<shrug> C++ is a living, evolving language. Maybe what you propose will be acceptable to the community in another 10 years. After all, that's about how long it took to get where we are today. ;-)Parrisch
"If your compiler is conforming (to any of C++98/03/11), then it will give the same output I show." How can you claim that, when C++98 is silent on the issue?Electronic
@curiousguy: Your claim is that C++98 was silent on the issue of "two-phase lookup" for templates?!Parrisch
@HowardHinnant For qualified names, obviously. Unqualified names are covered.Electronic
@NielKirk: What you are seeing as complication is simply too many wrong answers. There is nothing complicated about Dave Abrahams' correct answer: "The right way to overload swap is to write it in the same namespace as what you're swapping, so that it can be found via argument-dependent lookup (ADL)."Parrisch
See stackoverflow.com/questions/21384604/… - I've asked a StackOverflow question regarding specifically this answer.Aston
There is something complicated about this... you have to find this post to (a) know this solution is the currently recommended one (b) lose your fear of unknown consequences (c) believe in it.Sparky
@codeshot: Sorry. Herb has been trying to get this message across since 1998: gotw.ca/publications/mill02.htm He doesn't mention swap in this article. But this is just another application of Herb's Interface Principle.Parrisch
guru's are only a tiny part of the pool. stack overflow, google search with poorly formed queries, etc are how people get things done. I've been working in the software engineering industry for over 10 years and my colleagues (who I mostly learned from) didn't know this, didn't teach it, and (strongly and firmly) nudged in a different direction.Sparky
I get "exp::swap(A, A)" when using the the template<class T> struct A example.Eogene
@Pharap: Visual Studio?Parrisch
@HowardHinnant Yes.Eogene
Visual Studio does not yet correctly implement the 2-phase lookup rules introduced in C++98. That means that in this example VS calls the wrong swap. This adds a new wrinkle I hadn't previously considered: In the case of template<class T> struct A, putting your swap into namespace std renders your code non-portable. Try your example out on wandbox to see how gcc and clang handle it.Parrisch
@Eogene To make the long story short: Visual Studio C++ compiler's internal design made implementing this properly essentially impossible, and they kept it broken as long as they could, and only in the last few years their compiler team changed direction and took standards compliance seriously. I mean, how boneheaded can you be to knowingly not implement a rather fundamental part of C++ standard for ąpprox. two decades? MSVC used to be a C++ pariah. It's only recently that it's on par with open source compilers in terms of ability to deal with standard C++.Frederic
In the first example, is begin a dependent name or a non dependent name? I'm trying to understand two phase lookup and I think begin should be a dependent name so the actual instantiation should use the specialized overload?Demodulation
H
55

You're not allowed (by the C++ standard) to overload std::swap, however you are specifically allowed to add template specializations for your own types to the std namespace. E.g.

namespace std
{
    template<>
    void swap(my_type& lhs, my_type& rhs)
    {
       // ... blah
    }
}

then the usages in the std containers (and anywhere else) will pick your specialization instead of the general one.

Also note that providing a base class implementation of swap isn't good enough for your derived types. E.g. if you have

class Base
{
    // ... stuff ...
}
class Derived : public Base
{
    // ... stuff ...
}

namespace std
{
    template<>
    void swap(Base& lha, Base& rhs)
    {
       // ...
    }
}

this will work for Base classes, but if you try to swap two Derived objects it will use the generic version from std because the templated swap is an exact match (and it avoids the problem of only swapping the 'base' parts of your derived objects).

NOTE: I've updated this to remove the wrong bits from my last answer. D'oh! (thanks puetzk and j_random_hacker for pointing it out)

Hyozo answered 14/8, 2008 at 19:46 Comment(6)
Downvoted because the correct way to customize swap is to do so in your own namespace (as Dave Abrahams points out in another answer).Parrisch
Is it forbidden to overload std::swap (or anything else), but outside of std::swap namespace?Hegumen
@HowardHinnant, Dave Abrahams: I disagree. On what basis do you claim your alternative is the "correct" way? As puetzk quoted from the standard, this is specifically allowed. While I'm new to this issue I really don't like the method you advocate because if I define Foo and swap that way someone else who uses my code is likely to use std::swap(a, b) rather than swap(a, b) on Foo, which silently uses the inefficient default version.Rey
@Mozza314: The space and formatting constraints of the comment area did not allow me to fully reply to you. Please see the answer I've added titled "Attention Mozza314".Parrisch
@HowardHinnant, am I right in thinking this technique could also easily breach the one-definition-rule? If a translation unit has included <algorithm> and a forward declaration of class Base; whilst another includes the header, above, then you have two different instances of std::swap<Base>. I recall this is forbidden in a conforming program but using this technique means you must successfully prevent users of your class from writing a forward declaration - they must be forced somehow to always include your header to achieve their goals. This turns out to be impractical to achieve at scale.Sparky
I believe starting with C++20 it is only correct to specialize template classes, not template functions.Worden
O
31

While it's correct that one shouldn't generally add stuff to the std:: namespace, adding template specializations for user-defined types is specifically allowed. Overloading the functions is not. This is a subtle difference :-)

17.4.3.1/1 It is undefined for a C++ program to add declarations or definitions to namespace std or namespaces with namespace std unless otherwise specified. A program may add template specializations for any standard library template to namespace std. Such a specialization (complete or partial) of a standard library results in undefined behaviour unless the declaration depends on a user-defined name of external linkage and unless the template specialization meets the standard library requirements for the original template.

A specialization of std::swap would look like:

namespace std
{
    template<>
    void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}

Without the template<> bit it would be an overload, which is undefined, rather than a specialization, which is permitted. @Wilka's suggest approach of changing the default namespace may work with user code (due to Koenig lookup preferring the namespace-less version) but it's not guaranteed to, and in fact isn't really supposed to (the STL implementation ought to use the fully-qualified std::swap).

There is a thread on comp.lang.c++.moderated with a long dicussion of the topic. Most of it is about partial specialization, though (which there's currently no good way to do).

Overlying answered 20/9, 2008 at 22:24 Comment(6)
One reason it's wrong to use function template specialization for this (or anything): it interacts in bad ways with overloads, of which there are many for swap. For example, if you specialize the regular std::swap for std::vector<mytype>&, your specialization won't get chosen over the standard's vector-specific swap, because specializations aren't considered during overload resolution.Idoux
This is also what Meyers recommends in Effective C++ 3ed (Item 25, pp 106-112).Evangelize
@DaveAbrahams: If you specialize (without explicit template arguments), partial ordering will cause it to be a specialization of the vector version and it will be used.Misguided
@DavisHerring actually, no, when you do that partial ordering plays no role. The problem isn't that you can't call it at all; it's what happens in the presence of apparently-less-specific overloads of swap: wandbox.org/permlink/nck8BkG0WPlRtavVIdoux
@DaveAbrahams: The partial ordering is to select the function template to specialize when the explicit specialization matches more than one. The ::swap overload you added is more specialized than the std::swap overload for vector, so it captures the call and no specialization of the latter is relevant. I’m not sure how that’s a practical problem (but neither am I claiming that this is a good idea!).Misguided
@DavisHerring, ah, OK, yes; I didn't recall that there was a swap overload for std::vector. It's a practical problem because the programmer thinks he's customizing the behavior of swap for a specific type, but his customization can be rendered ineffective by a less-specific overload.Idoux

© 2022 - 2024 — McMap. All rights reserved.