简体   繁体   English

C ++-如何在自定义模板化数据容器中的迭代器上使用Advance()启用ADL?

[英]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): 我在上面的什么地方定义了advance(InputIt &, Distance N) ,以便允许通过ADL(依赖于参数的查找)在main()使用advance() ):

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 ? 并选择自定义的advance()函数而不是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. 我已经看到了在迭代器类中定义自定义advance()函数的示例,以及在迭代器类中仅声明友谊的情况下在名称空间中定义的示例。 Which is correct to enable use of ADL? 启用ADL哪个正确? Other examples on SO were not clear on this point. 关于SO的其他示例目前还不清楚。

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. 不合格的名称查找将考虑通过普通查找找到的任何内容(在您的情况下为函数模板std::advance )和通过ADL查找的所有内容(在您的情况下为advance(iterator&, Distance N)将通过重载解析进行考虑。在平等的基础上。

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. 如果iterator是类模板(或如图所示,是类模板的成员),则可以使您的advance成为类模板定义中定义的非模板朋友。

I believe the safest way is to define it friend of container or iterator . 我认为最安全的方法是将其定义为containeriterator friend The function defined in such way is put into namespace container_namespace so it can be found by ADL: 以这种方式定义的函数被放入namespace container_namespace因此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 . 另一个选择是直接在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: 这样,您可以对所有容器进行通用实现,并且/或者实现标记分派以处理不同的迭代器类别,就像在std::advance实现中那样:

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 这种方法的问题在于,当std::advance在范围内时可能会引起歧义(感谢@TC): DEMO

Note also, that you can't define advance as follows: 还要注意,您不能按以下方式定义advance

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. 但是,如果它是一个函数实现,则意味着该函数是(a)类的朋友,(b)不是类的成员,并且(c)无法从任何封闭的名称空间中访问。 ie. 即。 it becomes a global function which is only accessible via argument-dependent lookup (ADL). 它成为只能通过依赖于参数的查询 (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. 如果从main()内部调用advance() ,则ADL将起作用,并且将调用类迭代器的自定义高级。 However, if nsp::advance() , nsp::test_container<int>::advance() or stuff.advance() are tried, these will result in compile errors ("no matching function call"). 但是,如果尝试使用nsp::advance()nsp::test_container<int>::advance()stuff.advance() ,则会导致编译错误(“没有匹配的函数调用”)。

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. 虽然确实会优先使用非模板函数重载来调用模板函数重载,但这与ADL的使用无关。 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() . 另外, advance()特别需要距离类型的模板参数(int,long int,long long int等),由于我们不知道编译器将要推断出的类型,因此无法跳过此参数,说“ 1000000”,我们不知道程序员在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. 幸运的是,我们不必担心部分专业化,因为std::advance()与自定义高级变量位于不同的命名空间中,并且可以使用我们的硬编码迭代器类型简单地实现我们自己的advance() ,如上面的示例所示。

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. 尽管这并不特别涉及到advance()的实现,但它通常与类友函数的实现有关。 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. 您会注意到,在上面的示例中,我直接在迭代器类内部实现了非模板函数distance() ,而advance()模板的函数在迭代器类之外但在test_container类之内被声明为好友。 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. 但是, 可以在类外部声明模板的函数advance() ,而仅在好友类中包含定义。 The advance() function can also be implemented directed within the friend class, I just chose not to in order to illustrate this point. 还可以在friend类中直接实现advance()函数,我只是选择不这样做以说明这一点。

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. 但是上述方法将不起作用,因为编译器认为您对'element_type'和'element_allocator_type'使用相同的名称是对在test_container定义中首次使用的模板参数的重新定义,并且会引发错误。 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. 就是这样-我希望任何绊脚石的人都能从中得到利用-大多数信息以某种方式,形状或形式散布在stackoverflow上,但对新手而言,将其汇总起来很重要。

[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'. [更新:]即使上述所有内容,尽管它是“正确的”,但仍可能不足以将ADL正确解析为正确的功能。 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. 这是因为clang,Microsoft Visual Studio 2010-2013(可能还有其他语言)在解析复杂模板中的ADL时遇到困难,并且无论如何都可能崩溃或引发错误。 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: 您需要两件事来利用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 . 在这种特殊形式,因为它原来的模板参数ElementAllocator ,以advance不抵扣 In other words, advance is only callable if the caller passes in those parameters eg ns::advance<int, std::allocator<int>>(it, n) . 换句话说, advance是,如果调用者传递的那些参数仅可调用例如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. 由于通常不要求advance ,所以这是一个非常糟糕的候选人,我们可以完全排除它。

Inline friends 内联朋友

A short and sweet solution is to define a friend function inline inside iterator . 一个简短而又甜蜜的解决方案是在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. 这项技术的关键在于,它不定义函数模板,而是定义函数-vector vector<E, A>::iterator不是类模板而是其本身是一个类,每个vector<E, A>专业化。

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

Live On Coliru 生活在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 . advance由ADL发现,因为它是正确的命名空间,因为它是一个非模板函数是优于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. 嗯,有一个限制,就是您不能使用ns::advance的地址,实际上您根本无法命名。

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. 通常,您可以通过添加一个命名空间范围声明来使事情恢复正常 …除非我们不能直接使用,因为vector是一个类模板。 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. 实际上,当您初次接触班级模板和朋友时,您会遇到很多陷阱–例如,您可能会看到此合理的FAQ项并尝试利用它,只是发现它不适用于这种情况。

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 : 如果你真的做有关用户命名护理advance不合格调用外部(例如采取的地址或你有什么),我的建议是“解开” iteratorvector

// 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: 特别是,如果我们遵循上一个FAQ项的建议,则最终可能会出现以下形式:

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 . 值得指出的是,这显然是一个函数模板,由于部分排序规则 ,它将比例如std::advance更为可取。 Partial ordering is nearly always my preferred approach to the matter. 对于我而言,部分排序几乎总是我的首选方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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