Starting in C++20, the answer is unambiguously "yes, you can customize your own ADL iter_swap
." STL algorithms should use it, but aren't required to, and there is implementation divergence in practice.
For customizing a customization point, you should use the "hidden friend idiom":
namespace some_namespace {
template <class IterT>
class IndirectIterator {
IterT m_base;
public:
/* ... */
reference operator*() const { return **m_base; }
const base_iterator& base() const { return m_base; }
// "It's not a member, it's just a friend"
friend void iter_swap(IndirectIterator a, IndirectIterator b) {
using std::iter_swap;
iter_swap(a.base(), b.base());
}
};
}
Subtle change here: your iter_swap
had been taking its arguments by mutable reference! That wouldn't work, e.g. if it were passed an IndirectIterator
that was const, or that was an rvalue. Iterator parameters should always be taken by value. Similarly, there's no sense in providing a non-const version of base()
. Your base()
is a getter, not a setter; it should be a const member function for the same reason vector::size()
is a const member function.
Finally, the big caveat here is that you must never customize iter_swap
to do anything observably different from swapping the targets of the iterators. If you do that, then it's undefined behavior — all bets are off — the program could do anything. Formally, this requirement is given in [iterator.cust.swap]:
If the [iter_swap
] function selected by overload resolution does not exchange the values denoted by E1 and E2, the program is ill-formed, no diagnostic required.
I believe your idea of having iter_swap
swap the "first-level" targets rather than the "second-level" targets is perfectly kosher, as long as you're never going to rely on observing the difference. The standard library is still allowed to bypass iter_swap
and just do swap(*it, *kt)
if it feels like it; your code must be able to cope with that, or else "the program is ill-formed, no diagnostic required." (I.e., you must fully commit to the idea that swapping those first-level targets is tantamount to "exchanging the values of E1 and E2," and thus interchangeable with any other method of exchanging the values. You can't rely on your own method getting used.)
Here is a complete C++20 example on Godbolt. As of February 2023, here's where I see the customized iter_swap
actually getting used:
Algorithm |
libstdc++ |
libc++ |
Microsoft |
std::ranges::partition |
Yes |
Yes |
Yes |
std::ranges::sort |
No |
No |
Yes |
std::partition |
No |
No |
No |
std::sort |
No |
No |
No |