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

时间:2016-02-02 03:18:32

标签: c++ templates containers argument-dependent-lookup stdadvance

这是一个容器:

namespace container_namespace
{

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

    class iterator
    {
        // stuff
    };
};

}

在上面的位置我定义advance(InputIt &, Distance N)是为了允许通过ADL(依赖于参数的查找)在我的advance()中使用main()

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);
}

是否已选择自定义advance()功能而不是std::advance? 我已经看到了在迭代器类中定义的自定义advance()函数的示例,以及在命名空间内定义的示例,其中仅在迭代器类中声明了友谊。使用ADL哪个是正确的?关于SO的其他例子在这一点上并不清楚。

4 个答案:

答案 0 :(得分:3)

非限定名称查找将考虑普通查找(在您的情况下,函数模板std::advance)找到的任何内容以及ADL找到的内容(在您的情况下为advance(iterator&, Distance N)。)它们将被考虑在相同的基础上重载决议。

您的目标是确保自定义进度更匹配,最简单的方法是确保它是非模板功能:templates lose to non-templates if they are otherwise equally as good。如果您的iterator是类模板(或者,如图所示,是类模板的成员),您可以在类模板定义中定义advance非模板朋友。

答案 1 :(得分:2)

我认为最安全的方法是定义friendcontainer iterator。以这种方式定义的函数放入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

另一种选择可能是直接在namespace container_namespace中定义它。这样,您可以为所有容器实现通用实现和/或实现标记分派以处理不同的迭代器类别,就像在std::advance实现中所做的那样:

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

这种方法的问题在于std::advance在范围内时可能会导致歧义(感谢@TC): DEMO

另请注意,您无法按如下方式定义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";
    }
}

因为它的第一个参数的类型会失败(参见Non-deduced contexts)。

答案 2 :(得分:1)

虽然两个发布的答案都是正确的(而且我对两者都赞同)但我认为我会更深入地介绍这一点,对于今后发现这一点的人来说。

<强>&#39;朋友&#39;含义

首先,朋友&#39;对类中的函数有不同的含义。如果它只是一个函数声明,那么它将该给定函数声明为该类的朋友,并允许访问它的私有/受保护成员。但是,如果它是函数实现,则意味着函数是(a)类的朋友,(b)不是类的成员,(c)不能从任何封闭的命名空间中访问。即。它成为一个全局函数,只能通过argument-dependent lookup(ADL)访问。

以下面的测试代码为例:

#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;
}

如果在main()内调用advance(),则ADL将起作用,并且将调用类迭代器的自定义提前量。但是,如果尝试nsp::advance()nsp::test_container<int>::advance()stuff.advance(),则会导致编译错误(&#34;没有匹配的函数调用&#34;)。

模板问题

虽然确实会调用非模板函数重载而不是模板函数重载,但这与ADL使用无关。无论函数是模板还是非模板,都将调用特定类型的正确重载。另外,advance()特别需要距离类型的模板参数(int,long int,long long int等),不可能跳过这个,因为我们不知道编译器的类型推断,说&#34; 1000000&#34;,我们不知道程序员可能会在advance()抛出什么类型的类型。幸运的是,我们不需要担心部分特化,因为std::advance()与我们的自定义推进位于不同的命名空间中,并且可以使用我们的硬编码迭代器类型简单地实现我们自己的advance(),作为示例上面的节目。

如果我们的迭代器本身是一个模板并且接受参数,那么这仍然有效 - 我们只需在高级模板中包含参数并对模板进行硬编码并以这种方式输入迭代器。例如:

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

更多模板问题(旁注)

虽然这与advance()的实现没有具体关系,但它与一般的类朋友函数的实现有关。您将注意到在上面的示例中,我直接在迭代器类中实现了非模板函数distance(),而advance()模板函数在迭代器类之外被声明为朋友但在test_container类。这是为了说明一点。

如果类是模板(或模板的一部分),则不能在类之外的类之外实现非模板友元函数,因为编译器会抛出错误。但是,模板&#d} d函数advance() 可以在类外声明,只有友元类中包含的定义。 advance()函数也可以在朋友类中实现,我只是选择不是为了说明这一点。

模板朋友功能参数阴影

这与上面的示例无关,但对于程序员进入模板友好函数可能是一个陷阱。如果你有一个模板类,以及对该类进行操作的友元函数,显然你需要在函数定义和类中指定模板参数。例如:

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
    }

};

但是上述方法不起作用,因为编译器认为你使用的是&#39; element_type&#39;和&#39; element_allocator_type&#39;重新定义首先在test_container定义中使用的模板参数,并将抛出错误。因此,您必须为这些使用不同的名称。即。这有效:

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
    }

};

全部 - 我希望任何绊倒它的人都可以使用它 - 大部分信息都是以某种方式,形状或形式分布在stackoverflow中,但是将它们组合在一起对于新手来说非常重要。

[更新:]即使完全如上所述,尽管它是正确的,但仍然可能不足以正确地将ADL解析为正确的功能。这是因为clang,microsoft visual studio 2010-2013,可能是其他人,在复杂模板中解决ADL很困难,并且可能会崩溃或抛出错误。在这种情况下,简单地使用由迭代器类组成的标准容器函数是明智的。

答案 3 :(得分:1)

您需要两件事来利用ADL:

  • 将功能或功能模板置于右侧命名空间
  • 让功能或功能模板成为一个足够好的候选人

第一件事是直截了当的,但第二件事需要一点点照顾。这是你应该明确做的事情:

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)
{
    …
}

在这种特定形式中,模板参数ElementAllocatoradvance的结果为non-deducible。换句话说,advance只有在调用者传入这些参数时才可调用,例如ns::advance<int, std::allocator<int>>(it, n)。由于对advance的调用通常看起来不像这是一个非常可怕的候选人,我们可以直接排除它。

内联朋友

简短而甜蜜的解决方案是在iterator内定义内联的朋友函数。关于这种技术至关重要的是,这不是定义一个函数模板,而是一个函数 - 非常多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

ADL找到

advance,因为它是正确的命名空间,因为它是非模板函数,所以优先于std::advance。一切都在这片土地上,不是吗?嗯,有一个限制,你不能采用ns::advance的地址,事实上你根本就不能命名。

您通常可以通过添加命名空间范围声明来放置back to normal ...除了我们不能直接在我们的情况下,因为vector是一个类模板。事实上,当你第一次进入课堂模板和朋友时,你会遇到许多陷阱 - 例如。您可能会看到this reasonable FAQ item并尝试利用它,但发现它并不适用于此情况。

不是那么内联

如果你真的关心用户在不合格的电话之外命名advance(例如,取一个地址或你有什么地方),我的建议就是解开&#39;去解决问题。来自iterator的{​​{1}}:

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>;
    …
};

值得指出的是,这显然是一个功能模板,并且它将优先于例如由partial ordering rules引起的template<typename Element> void advance(vector_iterator<Element> it, int n); 。部分排序几乎总是我的首选方法。