简体   繁体   中英

C++ - How to enable ADL with advance() on iterators in custom templated data container?

Here's a container:

namespace container_namespace
{

template <class element_type, class element_allocator_type = std::allocator<element_type> >
class container
{
    // stuff

    class iterator
    {
        // stuff
    };
};

}

Where in the above do I define advance(InputIt &, Distance N) in order to be allowed to use advance() in my main() via ADL (Argument-dependent lookup):

int main(int argc, char **argv)
{
    using namespace std;
    using namespace container_namespace;

    container<int> c;

    // Add elements to c here

    container<int>::iterator it = c.begin();
    advance(it, 20);
}

And have the custom advance() function selected instead of std::advance ? I have seen examples of the custom advance() function being defined inside the iterator class, and examples where it was defined inside the namespace with only the friendship being declared inside the iterator class. Which is correct to enable use of ADL? Other examples on SO were not clear on this point.

Unqualified name lookup will consider both whatever found by ordinary lookup (in your case, the function template std::advance ) and what is found by ADL (in your case, advance(iterator&, Distance N) . They will be considered by overload resolution on equal grounds.

Your goal is to make sure that your custom advance is the better match, and the simplest way to do so is to make sure it is a non-template function: templates lose to non-templates if they are otherwise equally as good . If your iterator is a class template (or, as shown, a member of a class template), you could make your advance a non-template friend defined inside the class template definition.

I believe the safest way is to define it friend of container or iterator . The function defined in such way is put into namespace container_namespace so it can be found by ADL:

namespace container_namespace {
    template <class element_type, class element_allocator_type = std::allocator<element_type> >
    class container {
        //...
        template <typename Diff>
        friend void advance(iterator&, Diff) {
            //...
        }
    };
}

DEMO

Another option could be to define it directly in namespace container_namespace . This way you can have common implementation for all your containers and/or implement tag dispatch to handle different iterator categories, as it's done in std::advance implementation:

namespace container_namespace {
    template <typename Iter, typename Diff>
    void advance(Iter&, Diff) {
        std::cout << "ADL-ed advance\n";
    }
}

The problem with this approach is that it can cause ambiguity when std::advance is in scope (thanks, @TC): DEMO

Note also, that you can't define advance as follows:

namespace container_namespace {
    template <typename element_type, typename element_allocator_type, typename Diff>
    void advance(typename container<element_type, element_allocator_type>::iterator&, Diff) {
        std::cout << "ADL-ed advance\n";
    }
}

because the type of its first argument would fail (see Non-deduced contexts ).

Although both the posted answers are correct (and I have upvoted both) I thought I would cover this in a little more depth, for anyone who finds this in future.

'Friend' meanings

For starters, 'friend' has a different meaning on functions within a class. If it is simply a function declaration, then it declares the given function a friend of the class and allows access to it's private/protected members. If however it is a function implementation, it means the function is (a) a friend of the class, (b) not a member of the class and (c) not accessible from within any enclosing namespace. ie. it becomes a global function which is only accessible via argument-dependent lookup (ADL).

Take the following test code for example:

#include <iostream>
#include <iterator>

namespace nsp
{

template <class element_type, class element_allocator_type = std::allocator<element_type> >
class test_container
{
private:
    element_type numbers[50];
    friend class iterator;

public:
    class iterator : public std::iterator<std::bidirectional_iterator_tag, element_type>
    {
    private: 
        element_type *i;

        template <class distance_type>
        friend void advance(iterator &it, distance_type n);

        friend typename std::iterator_traits<iterator>::difference_type distance(const iterator &first, const iterator &last)
        {
            return last.i - first.i;
        }


    public: 

        iterator(element_type &_i)
        {
            i = &(_i);
        }

        element_type & operator *()
        {
            return *i;
        }

        element_type & operator = (const iterator source)
        {
            i = source.i;
            return *this;
        }

        bool operator != (const iterator rh)
        {
            return i != rh.i;
        }

        iterator & operator ++()
        {
            ++i;
            return *this;
        }

        iterator & operator --()
        {
            --i;
            return *this;
        }
    };


    iterator begin()
    {
        return iterator(numbers[0]);
    }


    iterator end()
    {
        return iterator(numbers[50]);
    }


    template <class distance_type>
    friend void advance(iterator &it, distance_type n)
    {
        it.i += 2 * n;
    }

};


}


int main(int argc, char **argv)
{
    nsp::test_container<int> stuff;

    int counter = 0;

    for (nsp::test_container<int>::iterator it = stuff.begin(); it != stuff.end(); ++it)
    {
        *it = counter++;
    }

    nsp::test_container<int>::iterator it = stuff.begin(), it2 = stuff.begin();

    using namespace std;

    cout << *it << endl;

    ++it;

    cout << *it << endl;

    advance(it, 2);

    cout << *it << endl;

    std::advance(it, 2);

    cout << *it << endl;

    int distance_between = distance(it2, it);

    cout << distance_between << endl;

    cin.get();

    return 0;
}

If, from within main() , advance() is called, ADL will function and the custom advance for the class iterator will be called. However, if nsp::advance() , nsp::test_container<int>::advance() or stuff.advance() are tried, these will result in compile errors ("no matching function call").

Template issues

While it is true that non-template function overloads will be called in preference of template function overloads, this is irrelevant for ADL usage. Regardless of whether the function is template or non-template, the correct overload for the specific type will be called. In addition, advance() specifically requires a template parameter of the distance type (int, long int, long long int etc), it is not possible to skip this because we don't know what type the compiler is going to infer from, say "1000000", and we don't know what kind of types the programmer might throw at advance() . Luckily we don't need to worry about partial specialization, as std::advance() is in a different namespace to our custom advance, and can simply implement our own advance() with our hardcoded iterator type, as the example above shows.

This still works if our iterator itself is a template and takes parameters - we simply include the parameters in the advance template and hardcode the template'd iterator type that way. eg.:

template <class element_type, class distance_type>
friend void advance(iterator<element_type>, distance_type distance);

More template issues (a side note)

While this doesn't relate specifically to the implementation of advance() it relates to the implementation of class friend functions in general. You will notice in the example above I implemented the non-template function distance() directly inside the iterator class, while the advance() template'd function is declared as a friend outside the iterator class but within the test_container class. This is to illustrate a point.

You cannot have a non-template friend function implemented outside the class it is friends with, if the class is a template (or part of a template) as your compiler will throw an error. However the template'd function advance() can be declared outside the class with only the definition included in the friend class. The advance() function can also be implemented directed within the friend class, I just chose not to in order to illustrate this point.

Template friend function parameter shadowing

This is not relevant to the above example but can be a pitfall for programmers stepping into template friend functions. If you have a template class, and a friend function which operates upon that class, obviously you are going to need to specify the template parameters in the function definition as well as the class. For example:

template <class element_type, class element_allocator_type = std::allocator<element_type> >
class test_container
{
private:
    element_type numbers[50];

public:
    template <class element_type, class element_allocator_type>
    friend void do_stuff(test_container<element_type, element_allocator_type> &container)
    {
        numbers[1] = 5; // or whatever
    }

};

However the above will not work because the compiler considers your using the same names for 'element_type' and 'element_allocator_type' to be a redefinition of the template parameters first used in the definition of test_container, and will throw an error. Therefore you must use different names for these. ie. this works:

template <class element_type, class element_allocator_type = std::allocator<element_type> >
class test_container
{
private:
    element_type numbers[50];

public:
    template <class c_element_type, class c_element_allocator_type>
    friend void do_stuff(test_container<c_element_type, c_element_allocator_type> &container)
    {
        numbers[1] = 5; // or whatever
    }

};

That's all- I hope anybody stumbling across this gets some use out of it - most of this information is spread out across stackoverflow in some way, shape or form, but bringing it together is important for the novice.

[UPDATE:] Even with all of the above, it still may not be enough to correctly resolve the ADL to the correct function, despite it being 'correct'. This is because clang, microsoft visual studio 2010-2013, possibly others, have difficulties resolving ADL in complex templates and may crash or throw errors regardless. In this case, you would be wise to simply resort to standard container functions which are friended by the iterator classes.

You need two things to take advantage of ADL:

  • have the function or function template live in the right namespace
  • have the function or function template be a good enough candidate

The first thing is straightforward, but the second one requires a little bit of care. Here is something you should definitively not do:

template<typename Element, typename Allocator>
struct vector {
    struct iterator {};
};

// trouble ahead!
template<typename Element, typename Allocator>
void advance(typename vector<Element, Allocator>::iterator it, int n)
{
    …
}

In this particular form, as it turns out the template parameters Element and Allocator to advance are non-deducible . In other words, advance is only callable if the caller passes in those parameters eg ns::advance<int, std::allocator<int>>(it, n) . Since calls to advance do not normally look like that this is a pretty awful candidate and we can outright rule it out.

Inline friends

A short and sweet solution is to define a friend function inline inside iterator . What's crucial about this technique is that this does not define a function template, but a function—very much how vector<E, A>::iterator is not a class template but is itself a class, one per vector<E, A> specialization.

template<typename Element, typename Allocator>
struct vector {
    struct iterator {
         friend void advance(iterator it, int n)
         { … }
    };
};

Live On Coliru

advance is found by ADL since it's the right namespace, and since it's a non-template function it is preferred over std::advance . All is well in the land, isn't it? Well, there is a limitation in that you cannot take the address of ns::advance , in fact you can't name it at all.

You can usually put things back to normal by adding a namespace-scope declaration… except we can't directly in our case because vector is a class template. In fact you run into many pitfalls when you first dwell into class templates and friends—eg you might see this reasonable FAQ item and try to take advantage of it, only to discover it's not applicable in the situation.

Not so inline

If you really do care about users naming advance outside of unqualified calls (eg to take an address or what have you), my advice is to 'disentangle' iterator from vector :

// might now need additional parameters for vector to fill in
template<typename Element>
struct vector_iterator;

template<typename Element, typename Allocator>
struct vector {
    using iterator = vector_iterator<Element>;
    …
};

In particular, if we follow the advice of the previous FAQ item we may end up with something of the form:

template<typename Element>
void advance(vector_iterator<Element> it, int n);

It is worth pointing out that this is obviously a function template, and it will be preferred over eg std::advance due to partial ordering rules . Partial ordering is nearly always my preferred approach to the matter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM