Can iter_swap be specialised?
Asked Answered
G

4

10

If I have a container std::vector<T*> items, I can create an IndirectIterator which wraps std::vector<T*>::iterator and allows iterating over T's rather than T*'s.

Can I specialise iter_swap for IndirectIterator to make standard algorithms (such as std::sort) swap items by pointer?

i.e., if I write the following, will it have any effect on standard algorithms?

namespace some_namespace
{
    template <typename IterT>
    class IndirectIterator
    {
            IterT m_base;
        public:
            typedef IterT base_iterator;
            typedef /* ... */ reference;

            /* ... */

            reference operator*() const { **m_base; }

            const base_iterator& base() const { return m_base; }
            base_iterator& base() { return m_base; }
    };

    template <typename T>
    void iter_swap(IndirectIterator<T>& a, IndirectIterator<T>& b)
    {
        using std::iter_swap;
        iter_swap(a.base(), b.base());
    }
}

The benefit of this specialisation is that it swaps pointers rather than full T instances, so it's faster (potentially).

Glennisglennon answered 1/9, 2012 at 11:53 Comment(0)
C
2

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
Cavuoto answered 22/2, 2023 at 15:49 Comment(0)
T
2

As far as I can see, iter_swap is only used in std::reverse, and it does not mention any kind of argument-dependent lookup: it always uses std::iter_swap. And since you are not allowed to overload functions in the std namespace, you're out of luck.

Toil answered 1/9, 2012 at 11:57 Comment(5)
Checking some headers from libc++ this looks correct, though it's a little disappointing. I guess I can do it with a custom reference class, but I'm not sure it's worth it just to make swap nicer.Glennisglennon
In C++11 (at least), std::sort uses std::iter_swap. (and I had to specialize/define std::iter_swap to make a certain special iterator work with std::sort.Blackball
You are still allowed to specialize templates in std namespace as long as they are specialized for user-defined arguments.Horselaugh
Also, the fact that one implementation only uses std::iter_swap in one place does not mean that's the only place it's used, ever, nor that that use is completely correct...Operant
@AnT and as long as you keep the same semantics as the non-specialized version.Toil
C
2

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
Cavuoto answered 22/2, 2023 at 15:49 Comment(0)
B
1

Can I specialise iter_swap for IndirectIterator to make standard algorithms (such as std::sort) swap items by pointer?

You can always make your overload/specialization. However your question is whether you can specialize iter_swap inside the namespace std.

I think the answer is unclear from the standard. I have found that in some cases, I had to define a special iter_swap inside std so that std::sort uses it. In gcc std lib std::sort uses the qualified std::iter_swap.

This is probably a defect in std::sort. IMO std::sort should call an unqualified swap_iter.

i.e., if I write the following, will it have any effect on standard algorithms?

Not in GCC (at least) because standard algorithms use qualified std::iter_swap (bug?) I don't think the standard is clear about it.

Blackball answered 7/11, 2016 at 7:43 Comment(0)
H
0

You are allowed to reopen std namespace and specialize templates inside std as long as you specialize them for user-defined types. In your case you can actually specialize std::iter_swap for your purposes, just make sure you are doing it in std namespace, not in your own namespace (as in your example). It is not very elegant, but it is allowed.

Horselaugh answered 7/11, 2016 at 7:52 Comment(1)
You're allowed to specialize certain class templates for your own types, e.g. std::hash<Mine>. You're not allowed to specialize other class templates, e.g. std::vector<Mine>; and you're never allowed to specialize function templates like iter_swap or make_pair or sort or min. In practice you physically can't, anyway, because you don't know their True Names (e.g. how many template parameters they have). You are allowed to customize the behavior of certain operations by overloading/ADL (swap, +, <, iter_swap, strong_order), but that's not "specializing templates."Cavuoto

© 2022 - 2024 — McMap. All rights reserved.