1

这是一个容器:

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)为了允许advance()在我的main()via ADL(依赖于参数的查找)中使用,我在上面定义了哪里:

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

4 回答 4

3

不合格的名称查找将考虑通过普通查找找到的任何内容(在您的情况下为函数 template std::advance)和 ADL 找到的内容(在您的情况下为advance(iterator&, Distance N). 它们将在平等的基础上被重载决议考虑。

你的目标是确保你的自定义Advance是更好的匹配,最简单的方法是确保它是一个非模板函数:如果模板在其他方面同样好,模板就会输给非模板。如果您iterator是类模板(或者,如图所示,类模板的成员),您可以advance在类模板定义中定义您的非模板朋友。

于 2016-02-02T04:07:06.310 回答
3

friend我相信最安全的方法是定义它containeror 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) {
            //...
        }
    };
}

演示

另一种选择是直接在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)。

于 2016-02-02T04:12:18.057 回答
2

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

“朋友”的含义

对于初学者来说,“朋友”对类中的函数有不同的含义。如果它只是一个函数声明,那么它将给定函数声明为类的朋友,并允许访问它的私有/受保护成员。然而,如果它是一个函数实现,则意味着该函数是 (a) 类的朋友,(b) 不是类的成员,并且 (c) 不能从任何封闭的命名空间中访问。IE。它成为一个全局函数,只能通过参数相关查找(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;
}

如果从inside 调用main()advance()ADL 将起作用,并且将调用类迭代器的自定义 Advance。但是,如果nsp::advance()尝试nsp::test_container<int>::advance()stuff.advance(),这些将导致编译错误(“没有匹配的函数调用”)。

模板问题

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

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

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

更多模板问题(附注)

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

如果该类是模板(或模板的一部分),则您不能在与它成为朋友的类之外实现非模板友元函数,因为您的编译器会抛出错误。然而,模板函数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
    }

};

但是,上面的方法不起作用,因为编译器认为您对 'element_type' 和 'element_allocator_type' 使用相同的名称是对 test_container 定义中首先使用的模板参数的重新定义,并且会引发错误。因此,您必须为它们使用不同的名称。IE。这有效:

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 是“正确的”,但仍不足以将 ADL 正确解析为正确的函数。这是因为 clang、microsoft visual studio 2010-2013,可能还有其他人,在复杂模板中解析 A​​DL 时遇到困难,并且无论如何都可能崩溃或抛出错误。在这种情况下,明智的做法是简单地求助于迭代器类友好的标准容器函数。

于 2016-02-08T02:40:51.003 回答
2

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

在这种特殊形式中,事实证明模板参数ElementAllocatortoadvance不可演绎的。换句话说,advance只有在调用者传入这些参数时才可调用,例如ns::advance<int, std::allocator<int>>(it, n). 由于调用advance通常看起来不像,这是一个非常糟糕的候选人,我们可以完全排除它。

内联好友

一个简短而甜蜜的解决方案是在内部定义一个友元函数 inline 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

advance由 ADL 找到,因为它是正确的命名空间,并且由于它是一个非模板函数,因此它优于std::advance. 在这片土地上一切都很好,不是吗?嗯,有一个限制,你不能取的地址ns::advance,事实上你根本不能命名它。

您通常可以通过添加命名空间范围声明来使事情恢复正常……除了我们不能直接在我们的例子中,因为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>;
    …
};

特别是,如果我们遵循上一个常见问题解答项目的建议,我们最终可能会得到以下形式的内容:

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

值得指出的是,这显然是一个函数模板,std::advance由于部分排序规则,它会比例如更受欢迎。部分排序几乎总是我首选的解决方法。

于 2016-02-09T04:31:11.077 回答