How to overload std::swap()

34,425

Solution 1

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);
        // ...
    }
};

Solution 2

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.

Solution 3

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)

Solution 4

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).

Share:
34,425
Adam
Author by

Adam

Software developer

Updated on October 13, 2021

Comments

  • Adam
    Adam over 2 years

    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?

  • JoeG
    JoeG almost 14 years
    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.
  • Dave Abrahams
    Dave Abrahams almost 14 years
    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.
  • Howard Hinnant
    Howard Hinnant about 13 years
    Downvoted because the correct way to customize swap is to do so in your own namespace (as Dave Abrahams points out in another answer).
  • Howard Hinnant
    Howard Hinnant about 13 years
    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.
  • Dave Abrahams
    Dave Abrahams about 13 years
    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.
  • Dave Abrahams
    Dave Abrahams about 13 years
    Somebody should check the library that ships with MSVC. I know they were a holdout for a long time on this one.
  • 0xbadf00d
    0xbadf00d about 13 years
    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.
  • Dave Abrahams
    Dave Abrahams about 13 years
    @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.
  • Kos
    Kos over 12 years
    Is it forbidden to overload std::swap (or anything else), but outside of std::swap namespace?
  • jww
    jww over 12 years
    This is also what Meyers recommends in Effective C++ 3ed (Item 25, pp 106-112).
  • JoeG
    JoeG over 12 years
    @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).
  • JoeG
    JoeG over 12 years
    Above comment refers to C++98 and C++03 - C++11 provides requirements for swappable types in section 17.6.3.2.
  • voltrevo
    voltrevo over 12 years
    @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?
  • voltrevo
    voltrevo over 12 years
    @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.
  • voltrevo
    voltrevo over 12 years
    -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.
  • JoeG
    JoeG over 12 years
    @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?
  • voltrevo
    voltrevo over 12 years
    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.
  • Howard Hinnant
    Howard Hinnant over 12 years
    @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".
  • voltrevo
    voltrevo over 12 years
    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?
  • voltrevo
    voltrevo over 12 years
    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?
  • Howard Hinnant
    Howard Hinnant over 12 years
    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).
  • voltrevo
    voltrevo over 12 years
    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?
  • Howard Hinnant
    Howard Hinnant over 12 years
    <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. ;-)
  • Dave Abrahams
    Dave Abrahams over 12 years
    @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.
  • curiousguy
    curiousguy almost 11 years
    @DaveAbrahams How what this ever "underspecified"? The standard just never allowed it! What is not allowed is forbidden.
  • curiousguy
    curiousguy almost 11 years
    "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?
  • Howard Hinnant
    Howard Hinnant almost 11 years
    @curiousguy: Your claim is that C++98 was silent on the issue of "two-phase lookup" for templates?!
  • curiousguy
    curiousguy almost 11 years
    @HowardHinnant For qualified names, obviously. Unqualified names are covered.
  • Dave Abrahams
    Dave Abrahams almost 11 years
    @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.
  • curiousguy
    curiousguy almost 11 years
    @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)
  • Howard Hinnant
    Howard Hinnant over 10 years
    @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)."
  • Dan Nissenbaum
    Dan Nissenbaum about 10 years
    See stackoverflow.com/questions/21384604/… - I've asked a StackOverflow question regarding specifically this answer.
  • codeshot
    codeshot over 8 years
    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.
  • Howard Hinnant
    Howard Hinnant over 8 years
    @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.
  • codeshot
    codeshot over 8 years
    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.
  • Paolo M
    Paolo M over 8 years
    @curiousguy Yes, he does have a source! He is Dave Abrahams
  • codeshot
    codeshot over 7 years
    @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.
  • Davis Herring
    Davis Herring about 6 years
    @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.
  • Dave Abrahams
    Dave Abrahams about 6 years
    @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/nck8BkG0WPlRtavV
  • Davis Herring
    Davis Herring about 6 years
    @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!).
  • Dave Abrahams
    Dave Abrahams about 6 years
    @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.
  • Pharap
    Pharap almost 6 years
    I get "exp::swap(A, A)" when using the the template<class T> struct A example.
  • Howard Hinnant
    Howard Hinnant almost 6 years
    @Pharap: Visual Studio?
  • Pharap
    Pharap almost 6 years
    @HowardHinnant Yes.
  • Howard Hinnant
    Howard Hinnant almost 6 years
    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.
  • Chris_F
    Chris_F almost 5 years
    I believe starting with C++20 it is only correct to specialize template classes, not template functions.
  • Kuba hasn't forgotten Monica
    Kuba hasn't forgotten Monica over 3 years
    @Pharap 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++.
  • PYA
    PYA over 2 years
    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?
  • Luis Ayuso
    Luis Ayuso about 2 years
    one question. what is the purpose of friend here?