[英]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
成为类模板定义中定义的非模板朋友。
我认为最安全的方法是将其定义为container
或iterator
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)
{
…
}
在这种特殊形式,因为它原来的模板参数Element
和Allocator
,以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)
{ … }
};
};
advance
由ADL发现,因为它是正确的命名空间,因为它是一个非模板函数是优于std::advance
。 土地上一切都很好,不是吗? 嗯,有一个限制,就是您不能使用ns::advance
的地址,实际上您根本无法命名。
通常,您可以通过添加一个命名空间范围声明来使事情恢复正常 …除非我们不能直接使用,因为vector
是一个类模板。 实际上,当您初次接触班级模板和朋友时,您会遇到很多陷阱–例如,您可能会看到此合理的FAQ项并尝试利用它,只是发现它不适用于这种情况。
如果你真的做有关用户命名护理advance
不合格调用外部(例如采取的地址或你有什么),我的建议是“解开” iterator
从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>;
…
};
特别是,如果我们遵循上一个FAQ项的建议,则最终可能会出现以下形式:
template<typename Element>
void advance(vector_iterator<Element> it, int n);
值得指出的是,这显然是一个函数模板,由于部分排序规则 ,它将比例如std::advance
更为可取。 对于我而言,部分排序几乎总是我的首选方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.