繁体   English   中英

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

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

这是一个容器:

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(依赖于参数的查找)在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);
}

并选择自定义的advance()函数而不是std::advance吗? 我已经看到了在迭代器类中定义自定义advance()函数的示例,以及在迭代器类中仅声明友谊的情况下在名称空间中定义的示例。 启用ADL哪个正确? 关于SO的其他示例目前还不清楚。

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

您的目标是确保自定义进度是更好的匹配,而最简单的方法是确保它是非模板功能: 如果模板与其他模板一样好,则它们会输给非模板 如果iterator是类模板(或如图所示,是类模板的成员),则可以使您的advance成为类模板定义中定义的非模板朋友。

我认为最安全的方法是将其定义为containeriterator friend 以这种方式定义的函数被放入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) {
            //...
        }
    };
}

演示

另一个选择是直接在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";
    }
}

因为它的第一个参数的类型将失败(请参阅非推论上下文 )。

尽管发布的两个答案都是正确的(并且我都赞成),但我认为我会更深入地介绍这一点,以供将来发现此问题的任何人使用。

“朋友”的意思

对于初学者来说,“朋友”在类中的功能上具有不同的含义。 如果仅是函数声明,则它将给定函数声明为该类的朋友,并允许访问其私有/受保护的成员。 但是,如果它是一个函数实现,则意味着该函数是(a)类的朋友,(b)不是类的成员,并且(c)无法从任何封闭的名称空间中访问。 即。 它成为只能通过依赖于参数的查询 (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() ,则会导致编译错误(“没有匹配的函数调用”)。

模板问题

虽然确实会优先使用非模板函数重载来调用模板函数重载,但这与ADL的使用无关。 无论函数是模板还是非模板,都将调用特定类型的正确重载。 另外, advance()特别需要距离类型的模板参数(int,long int,long long int等),由于我们不知道编译器将要推断出的类型,因此无法跳过此参数,说“ 1000000”,我们不知道程序员在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类之内被声明为好友。 这是为了说明一点。

如果类是模板(或模板的一部分),则不能在与之成为朋友的类之外实现非模板朋友功能,因为编译器会抛出错误。 但是, 可以在类外部声明模板的函数advance() ,而仅在好友类中包含定义。 还可以在friend类中直接实现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
    }

};

但是上述方法将不起作用,因为编译器认为您对'element_type'和'element_allocator_type'使用相同的名称是对在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时遇到困难,并且无论如何都可能崩溃或引发错误。 在这种情况下,明智的做法是简单地使用迭代器类友好的标准容器函数。

您需要两件事来利用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)
{
    …
}

在这种特殊形式,因为它原来的模板参数ElementAllocator ,以advance不抵扣 换句话说, advance是,如果调用者传递的那些参数仅可调用例如ns::advance<int, std::allocator<int>>(it, n) 由于通常不要求advance ,所以这是一个非常糟糕的候选人,我们可以完全排除它。

内联朋友

一个简短而又甜蜜的解决方案是在iterator内联定义一个友好函数。 这项技术的关键在于,它不定义函数模板,而是定义函数-vector vector<E, A>::iterator不是类模板而是其本身是一个类,每个vector<E, A>专业化。

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

生活在Coliru

advance由ADL发现,因为它是正确的命名空间,因为它是一个非模板函数是优于std::advance 土地上一切都很好,不是吗? 嗯,有一个限制,就是您不能使用ns::advance的地址,实际上您根本无法命名。

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

不太内联

如果你真的做有关用户命名护理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>;
    …
};

特别是,如果我们遵循上一个FAQ项的建议,则最终可能会出现以下形式:

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

值得指出的是,这显然是一个函数模板,由于部分排序规则 ,它将比例如std::advance更为可取。 对于我而言,部分排序几乎总是我的首选方法。

暂无
暂无

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

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