Search code examples
c++language-lawyerswap

Rationale behind the usual implemention of std::swap overloads


Let's consider the following minimal code sample:

// Dummy struct
struct S
{
    int a;
    char * b;

    // Ctors, etc...
};

void swap(S & lhs, S & rhs)
{
    using std::swap;

    swap(lhs.a, rhs.a);
    swap(lhs.b, rhs.b);
}

Context

I know that when we intend to call a swap() function, the proper and recommended way to proceed is to do as follows:

// Let's assume we want to swap two S instances.
using std::swap;
swap(s1, s2);

This way, ADL is able to find the proper overload if any (defined in the given type namespace), otherwise std::swap is used.

Then I read some SO threads:

where the same idiom is used (even for built-in or pointer types), but not only when we call the std::swap() overload but also when we define it.

Question

If the members are all built-in types (or pointer types, possibly pointers to custom types), is it still necessary/recommended to use that idiom instead of explicitly calling std::swap() ?

For example, considering the small code sample I provided, since S members are built-in types, is it fine (well-formed) to define it as below instead:

void swap(S & lhs, S & rhs)
{
    std::swap(lhs.a, rhs.a);
    std::swap(lhs.b, rhs.b);
}

Or maybe is there a reason I couldn't see that would still require to use the using std::swap; way ?


Solution

  • since S members are built-in types, is it fine (well-formed) to define it as below instead: [std::swap usage]

    Yes. I mean, it works because std::swap just "swaps the values a and b" if they are both MoveAssignable and MoveConstructible, which built-in types are.

    If the members are all built-in types (or pointer types, possibly pointers to custom types), is it still necessary/recommended to use that idiom instead of explicitly calling std::swap() ?

    Just for the future of your class: if one day any of its members gets replaced with a user-defined type, you might forget to modify your swap and miss a "specialized" ADL implementation for it, still calling the generic std::swap which could (theoretically) be inefficient yet still compile.

    Actually, you don't even need to think about it if you just always use boost::swap or implement it yourself:

    template<typename T> void swap(T& a, T& b) {
        using std::swap;
        swap(a, b);
    }