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的其他例子在这一点上并不清楚。
最安全的方法是将其定义为friend
container
或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 advancen";
}
}
这种方法的问题在于,当std::advance
在范围内时,它可能会导致歧义(谢谢,@TC(:演示
另请注意,不能按如下方式定义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 advancen";
}
}
因为它的第一个参数的类型会失败(请参阅非推导上下文(。
非限定名称查找将考虑普通查找找到的任何内容(在您的情况下,函数模板std::advance
(和 ADL 找到的内容(在您的情况下,advance(iterator&, Distance N)
.它们将通过基于相同理由的过载解决方案来考虑。
您的目标是确保您的自定义高级版是更好的匹配,最简单的方法是确保它是一个非模板函数:如果模板在其他方面同样好,则它们会输给非模板。如果iterator
是类模板(或者如图所示,是类模板的成员(,则可以将advance
设置为类模板定义中定义的非模板友元。
尽管发布的两个答案都是正确的(而且我都投了赞成票(,但我想我会更深入地介绍这一点,供将来发现这一点的人使用。
"朋友"的含义
对于初学者来说,"friend"在类中的函数上有不同的含义。如果它只是一个函数声明,那么它将给定的函数声明为类的友元,并允许访问它的私有/受保护成员。但是,如果它是一个函数实现,则意味着该函数是(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()
template'd 函数被声明为迭代器类之外但在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定义中首次使用的模板参数的重新定义,并且会引发错误。因此,您必须为这些使用不同的名称。即。这有效:
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(可能还有其他(在复杂模板中解析 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<E, A>::iterator
不是类模板,而是它本身是一个类,每个vector<E, A>
专用化一个。
template<typename Element, typename Allocator>
struct vector {
struct iterator {
friend void advance(iterator it, int n)
{ … }
};
};
住在科里鲁
ADL 可以找到advance
,因为它是正确的命名空间,并且由于它是一个非模板函数,因此它优先于 std::advance
.这片土地上一切都很好,不是吗?好吧,有一个限制,你不能拿ns::advance
的地址,实际上你根本无法命名它。
您通常可以通过添加命名空间范围的声明来使事情恢复正常...除了我们不能直接在我们的例子中,因为vector
是一个类模板。事实上,当你第一次沉浸在课堂模板和朋友中时,你会遇到很多陷阱——例如,你可能会看到这个合理的常见问题解答项目并试图利用它,却发现它不适用于这种情况。
不是那么内联
如果你真的关心用户在不合格的电话之外命名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>;
…
};
特别是,如果我们遵循上一个常见问题解答项的建议,我们最终可能会得到以下形式:
template<typename Element>
void advance(vector_iterator<Element> it, int n);
值得指出的是,这显然是一个函数模板,并且比例如 std::advance
由于部分排序规则。部分排序几乎总是我的首选方法。
- 使用std::multimap迭代器创建std::list
- 来自 std::list 的迭代器 .end() 按预期返回"0xcdcdcdcdcdcdcdcd"但 .begin()
- C++中带有List类的迭代器Segfault
- 如何在c++迭代器类型中包装std::chrono
- 集合上的输出迭代器:assign和increment迭代器
- Boost Spirit,获取迭代器内部语义动作
- 对于set上的循环-获取next元素迭代器
- 为什么output_editor Concept不需要output_e迭代器标记
- c++17文件系统::recursive_directory迭代器()在mac上没有给出这样的目录,但在windows上
- 使用迭代器时如何访问对象在向量中的位置?
- std::vector::迭代器是否可以合法地作为指针
- 跟随整数索引列表的自定义类迭代器
- 不明白迭代器,引用和指针失效,一个例子
- 我可以使用反向迭代器作为ForwardIt吗
- C++ - 如何在自定义模板化数据容器中的迭代器上使用 advance() 启用 ADL
- 你能在字符串中使用 advance() 吗::迭代器 在C++
- 如何告诉 advance() 在输入迭代器上使用 += 运算符(如果它们不是随机访问)
- 函数与advance类似,返回迭代器
- 如何实现std::advance来改变迭代器类型上的行为
- 为什么 std::advance 不返回生成的迭代器