What Does 'Using Std::Swap' Inside the Body of a Class Method Implementation Mean

what does `using std::swap` inside the body of a class method implementation mean?

This mechanism is normally used in templated code, i.e. template <typename Value> class Foo.

Now the question is which swap to use. std::swap<Value> will work, but it might not be ideal. There's a good chance that there's a better overload of swap for type Value, but in which namespace would that be? It's almost certainly not in std:: (since that's illegal), but quite likely in the namespace of Value. Likely, but far from certain.

In that case, swap(myValue, anotherValue) will get you the "best" swap possible. Argument Dependent Lookup will find any swap in the namespace where Value came from. Otherwise the using directive kicks in, and std::swap<Value> will be instantiated and used.

In your code, mSize is likely an integral type, and mArray a pointer. Neither has an associated namespace, and std::swap is with 99.9% certainty optimal for them anyway. Therefore, the using std::swap; declaration seems useless here.

How does using std::swap enable ADL?

The "enable ADL" comment applies to the transformation of

std::swap(first.mSize, second.mSize);
std::swap(first.mArray, second.mArray);

to

using std::swap;
swap(first.mSize, second.mSize);
swap(first.mArray, second.mArray);

You're right, ADL only requires an unqualified name, but this is how the code is re-worked to use an unqualified name.

Just plain

swap(first.mSize, second.mSize);
swap(first.mArray, second.mArray);

wouldn't work, because for many types, ADL won't find std::swap, and no other usable swap implementation is in scope.

What's the difference between std::ranges::swap() and std::swap()?

In rough terms: std::ranges::swap generalizes std::swap.

  • If T and U are the same, then it's equivalent to invoking std::swap.
  • Otherwise it will swap the arguments as two ranges - if they're ranges.
  • Otherwise it will perform a swap-like procedure with move assignment between the different types T and U.
  • If even that can't happen, it will fail to compile.

See the cppreference page for details.

I must say I find this function to be somewhat confusing and baroque, but then - maybe people more experienced with the range library develop an intuition as to why it makes sense.

The c++ using statement within a function, followed by a function name (for ADL?)

The using statement makes this line work:

swap(first, second);

Notice that we can omit std:: in front of swap.

The important thing there is that std::swap(...) is a qualified lookup, but swap(...) is an unqualified lookup. The main difference is that qualified lookup is calling a function in a specific namespace or scope (the one specified), whereas unqualified lookup is a bit more flexible since it will look into parent scope of the current context and also the global namespace. In addition, unqualified lookup will also look into the scope of the type of the arguments. It's a nice tool, but also dangerous since it can call function from unexpected places.

ADL will only work with unqualified lookup, since it has to search for other namespaces and scopes.

The using std::swap also ensure that if no function is found through ADL, it will call std::swap by default.

This idiom allow for user defined swap functions:

struct MyType {
// Function found only through ADL
friend void swap(MyType& l, MyType& r) {
// ...
}
};

Use std::swap between vectors or vector::swap?

You should not use std::swap() directly in any case! Instead, you should use something like this:

using std::swap;
swap(x, y);

For std::vector<...> it probably doesn't make a difference as std::vector<...> obviously lives in namespace std. Otherwise the key difference is that with using std::swap() the default implementation is being used while with the approach outlined about ADL can find a better version.

Using swap(x, y) for std::vector<...>s x and y will just call x.swap(y). For consistency with other uses I would use the approach listed above.


References:

  • How does "using std::swap" enable ADL?
  • what does `using std::swap` inside the body of a class method implementation mean?

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.

Providing swap() for a C++ template class breaks std::swap()?

The approach taken by STL containers uses a member function and then overload the static function. For example:

template<class T, class Alloc=std::allocator<T> >
class vector
{
T *data;
size_t n;
size_t max_n;
public:
void swap(vector<T, Alloc> &other)
{
swap(this->data, other.data);
swap(this->n, other.n);
swap(this->max_n, other.max_n);
}
};

template<class T, class A>
void swap(vector<T, A> &lhs, vector<T, A> &rhs)
{
lhs.swap(rhs);
}

In the suggested Matrix class, simply take the same approach...

namespace my_space
{
template<typename T>
class Matrix
{
unsigned width_;
unsigned height_;
std::vector<T> data_;
public:
void swap(Matrix<T> &other)
{
std::swap(this->width_, other.width_);
std::swap(this->height_, other.height_);
std::swap(this->data_, other.data_); // calls this->data_.swap(other.data_);
}
};
}

namespace std
{
template<typename T>
void swap(my_space::Matrix<T> &lhs, my_space::Matrix<T> &rhs)
{
lhs.swap(rhs);
}
}

copy and swap idiom with pure virtual class

As your compiler informs you, you cannot create a variable of abstract type. There is no way of dancing around that.

This leaves you three main options:

Stop using pure virtual functions

First, you could just get rid of the pure virtual methods and provide a little stub in each of them that calls std::terminate, which would obviously break compile time detection of whether all (former) pure virtual methods are overridden in all derived classes.

This will cause slicing, since it will only copy the base class and everything that makes out the derived class is lost.

Use a stub class w/o pure virtual functions

Similar to that, you could create a derived class that implements all virtual methods with simple stubs (possibly calling std::terminate), and is used only used as a "instantiatable version of the base class".

The most important part to implement for this class would be a constructor that takes a const reference to the base class, so you can just use it instead of copying the base class. This example also adds a move constructor, because I am a performance fetishist.

This causes the same slicing problem as the first option. This may be your intended result, based on what you are doing.

struct InstantiatableA : public A {
InstantiatableA(A const& rhs) : A(rhs) { }
InstantiatableA(A&& rhs) : A(::std::move(rhs)) { }

void print(ostream&) override { ::std::terminate(); }
};

A& A::operator=(InstantiatableA rhs) {
using ::std::swap;
swap(*this, rhs);
return *this;
}

Note: This is really a variable of type A, although I said it could not be done. The only thing you have to be aware is that the variable of type A lives inside a variable of type InstantiatableA!

Use a copy factory

Finally, you can add a virtual A* copy() = 0; to the base class. Your derived class B will then have to implement it as A* copy() override { return new B(*this); }. The reason dynamic memory is necessary is because your derived types may require arbitrarily more memory than your base class.

Is this a proper implementation of the Rule of Five (or Rule of Four and 1/2)?

Most important of all:

  • This class doesn't need custom copy/move operations nor the destructor, so rule of 0 should be followed.

Other things:

  • I don't like swap(*this, other); in the move ctor. It forces members to be default-constructed and then assigned. A better alternative would be to use a member initializer list, with std::exchange.

    If initializing all members gets tedious, wrap them in a structure. It makes writing the swap easier too.

  • Copy constructor must take the parameter by a const reference.

  • unique_ptrs which can't be copied. So we move it. is a bad rationale. If your members can't be copied, don't define copy operations. In presence of custom move operations, the copy operations will not be generated automatically

  • Move operations (including the by-value assignment) should be noexcept, because standard containers won't use them otherwise in some scenarios.

  • SomeClass() = default; causes members that are normally uninitialized (int m_int;) to sometimes be zeroed, depending on how the class is constructed. (E.g. SomeClass x{}; zeroes it, but SomeClass x; doesn't.)

    Unless you want this behavior, the constructor should be replaced with SomeClass() {}, and m_int should probably be zeroed (in class body).



Related Topics



Leave a reply



Submit