As proper use of std::swap is:
using std::swap;
swap(a,b);
It is a bit verbose but it makes sure that if a,b have better swap defined it gets picked.
So now my question is why std::swap
is not implemented using this technique so user code would just need to call std::swap
?
So something like this (ignoring noexcept
and constraints for brevity):
namespace std {
namespace internal {
template <class T> // normal swap implementation
void swap(T& a, T& b) { // not intended to be called directly
T tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
}
}
template <class T>
void swap(T& a, T& b) {
using internal::swap;
swap(a,b);
}
}
swap() in C++ The function std::swap() is a built-in function in the C++ Standard Template Library (STL) which swaps the value of two variables. Parameters: The function accepts two mandatory parameters a and b which are to be swapped. The parameters can be of any data type.
Copy-and-Swap Idiom in C++ But when overloading the assignment operator, it can become quite difficult to implement. The copy and swap idiom is a solution for the same. This idiom uses the copy-constructor to build a local copy of the data. It then swaps the old data with the new data using the swap function.
This is getting into tautology territory, but it doesn't do it that way because that's not its purpose.
The purpose of std::swap
is to be the swap function of last resort. It's not supposed to be the thing you call directly, unless what you really want is to use the swap-of-last-resort.
Now, you can argue that what you suggest is a better paradigm for customization points. Hindsight is always 20/20; not everything that STL did was the right idea (see vector<bool>
).
As for why we can't change that now, that's a different matter. std::swap
is the swap function of last resort. So it is theoretically possible that people are calling it and expecting it to bypass any user-defined swap code. And therefore, changing it in this way breaks their code.
Here's one problem with this approach. The ADL "two-step" relies on the function that ADL finds to be a better match than the normal one (otherwise, you'd get an overload resolution failure). This is fine most of the time, since when you write your swap()
for your user-defined types in your user-defined namespaces, you're writing functions specific to those types.
But for standard library types, that may not have a more efficient swap()
than the simple algorithm, this breaks down. Example:
namespace N {
namespace internal {
template <typename T>
void swap(T&, T&); // normal swap impl
}
template <typename T>
void swap(T& a, T& b) {
using internal::swap;
swap(a, b); // (*)
}
struct C { };
}
N::C c;
swap(c, c); // error
N::swap(c, c); // error
Both of these calls fail, for the same reason. The unqualified call to swap()
marked (*)
will find N::internal::swap()
through normal unqualified lookup, and then find N::swap()
through ADL. There is no way to differentiate between these calls, since you very much need both calls to work for all types that meet swap
's constraints. The resulting call is ambiguous.
So such a design would necessitate adding a new swap()
function for every type in namespace std
, even if it would just forward to std::internal::swap()
.
Historically, there was not much thought on name resolution, it seems. std::swap
was designed as a customization point, but also wound up as the function that one would call in generic code to swap things. Hence, if std::swap
didn't work, or was too slow, then one might have had to overload it, even if there already was a perfectly good swap
to be found with ADL. This design cannot be changed without breaking or changing the meaning of existing code. Now there have been cases where the committee gladly decided to change the meaning of existing code for the sake of performance, such as implicit move semantics (e.g. when passing temporaries by value, or RVO where elision is not implemented). So this is not entirely consistent. Nonetheless, changing std::swap
from a customization point to a name resolution wrapper with all the existing std::swap
overloads in existence is dubious. This could absolutely cause catastrophic bugs to be triggered in poorly written legacy code.
Another important reason is, IMO, that you cannot move this idiom to the std
namespace, while maintaining its generality. E.g.:
namespace A { struct X{}; }
namespace B {
using std::swap;
void swap(A::X&, A::X&);
template<typename T>
void reverse(T (&ts)[4])
{
swap(ts[0], ts[3]);
swap(ts[1], ts[2]);
}
void silly(A::X (&xs)[4])
{
reverse(xs);
}
}
Here, silly
winds up using B::swap
. The reason for this is that std::swap
(via using
) and B::swap
are both visible with the same precedence, but the latter is a better match. Now, you may argue that this is a silly example, so here's another that is a bit less contrived:
namespace types { /*...*/ }
namespace algorithms { /*including some swap implementations for types from above...*/ }
template<typename T>
void reverse(T (&ts)[4])
{
using std::swap;
using algorithms::swap;
swap(ts[0], ts[3]);
swap(ts[1], ts[2]);
}
This will use a swap function from algorithms
if it is a better match than any std::swap
overload, but algorithms::swap
will not be found by ADL, therefore the lookup cannot happen inside std::swap
, generically. An arbitrary number of namespaces could be involved here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With