如何处理多种迭代器类型

发布于 2024-12-21 11:40:45 字数 1154 浏览 1 评论 0原文

我有一个自定义数据结构,可以通过多种方式访问​​它。我想尽量保持这个数据结构符合 STL 标准。所以我已经有很多 typedef,它们为模板参数提供了 STL 名称。现在对我来说一切如常。

但是我不太确定如何正确地将迭代器添加到我的数据结构中。我面临的主要问题是,数据结构上会有多个迭代策略。最简单的用例是迭代所有元素,这可以通过数据结构上符合 STL 的迭代器很好地处理。然而,人们可能还想访问在某种程度上类似于给定键的元素。我还想以一种可以与 STL 交互的方式迭代所有这些相似的元素。

到目前为止,这些是我考虑过的想法:

  • 仅提供一种类型的迭代器

这基本上就是 std::map 的作用。子范围的开始和结束迭代器由 std::map::lower_bound()std::map::upper_bound() 提供。

不过,这效果很好,因为 begin()end()lower_bound()upper_bound()< 返回的迭代器/code> 是兼容的,即 operator==() 可以被赋予非常明确的含义。就我而言,这很难得到正确的结果,甚至可能无法给出一些清晰的语义。例如,我可能会遇到 it1==it2++it1!=++it2 的情况。我不确定STL是否允许这样做。

  • 提供多种类型的迭代器

更容易提供干净的operator==()语义。另一方面令人讨厌,因为它增加了类型的数量。

  • 提供一种类型的迭代器并使用 stl::algorithms 进行专门访问

我不确定这是否可行。迭代状态应该由迭代器以某种方式保存(直接保存或在备忘录中保存)。使用这种方法意味着专门化所有 stl:: 算法并直接在专门化中访问谓词。很可能是不可能的,如果可能的话,这是一个非常糟糕的主意。

现在我主要选择版本 1,即只提供一种类型的迭代器。然而,由于我不清楚如何清理语义,所以我还没有决定。

你会如何处理这个问题?

I have a custom datastructure, which can be accessed in multiple ways. I want to try to keep this datastructure to keep to STL-standards as well as possible. So I already have lot's of typedefs, which give template parameters the STL-names. This is business as usual for me by now.

However I am not so sure how to correctly add iterators to my datastructure. The main problem I am facing is, that there would be multiple iteration policies over the datastructure. The easiest use case is iterating over all elements, which would be handled well by STL-Conforming iterators over the datastructure. However one might also want to access elements, which are somehow similar to a given key. I would also like to iterate over all these similar elements in a way which I can interface with the STL.

These are the ideas I have thought about so far:

  • Provide only one type of iterator:

This is basicly what std::map does. The start and end iterators for a subrange are provided by std::map::lower_bound() and std::map::upper_bound().

However this works well, because the iterators returned by begin(), end(), lower_bound() and upper_bound() are compatible, i.e. the operator==() can be given a very well defined meaning on these. In my case this would be hard to get right, or might even be impossible to give some clear semantics. For example I probably would get some cases where it1==it2 but ++it1!=++it2. I am not sure if this is allowed by the STL.

  • Provide multiple types of iterators:

Much easier to provide clean operator==() semantics. Nasty on the other hand because it enlarges the number of types.

  • Provide one type of iterator and use stl::algorithms for specialized access

I am not sure if this is possible at all. The iteration state should be kept by the iterator somehow (either directly or in a Memento). Using this approach would mean to specialize all stl::algorithms and access the predicate directly in the specialization. Most likely impossible, and if possible a very bad idea.

Right now I am mostly opting for Version 1, i.e. to provide only one type of iterator at all. However since I am not clear on how to clean up the semantics, I have not yet decided.

How would you handle this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

请远离我 2024-12-28 11:40:45

标准容器支持具有两种迭代器类型的两种迭代策略:::iterator::reverse_iterator。您可以使用 std::reverse_iterator 的构造函数及其成员函数 base() 在两者之间进行转换。

根据迭代策略的相似程度,提供不同迭代器类型的转换可能很容易,也可能不容易。这个想法是结果应该指向目标类型的迭代策略中的“等效位置”。对于反向迭代器,这种等价性的定义是,如果在该点插入,结果是相同的。因此,如果 rit 是反向迭代器,则 vec.insert(rit.base(), ...) 会在 rit 之前插入一个元素在反向迭代中,也就是说容器中rit指向的元素之后。这是非常繁琐的,并且当迭代策略完全不相关时只会变得更糟。但是,如果所有迭代器类型都是(或可以使其看起来像)围绕遍历所有元素的“正常”迭代器的包装器,那么您可以根据基础迭代器位置定义转换。

如果存在添加或删除容器元素的成员函数,您实际上只需要转换,因为您可能不希望必须为每个迭代器类型提供单独的重载(就像标准容器不提供单独的重载一样)不为反向迭代器定义 inserterase)。如果迭代器仅用于指向元素,那么很可能您可以不用它们。

如果不同的迭代策略都按正常顺序对元素子集进行迭代,则查看 boost::filter_iterator

我可能会遇到 it1==it2++it1!=++it2 的情况。我是
不确定 STL 是否允许这样做。

如果我理解正确的话,你从 thing.normal_begin() 开始得到 it1,从 thing 开始得到 it2。 other_policy_begin()。标准容器没有定义比较属于不同范围的相同类型的迭代器的结果,因此,如果您确实使用通用类型,那么我认为只要文档明确说明尽管 operator == 确实有效,范围根据迭代器的来源而分开。

例如,您可以有一个 skip_iterator ,它将每次调用 ++ 时应向前​​移动的步数作为构造函数参数。然后,您可以在比较中包含该整数,以便 thing.skip_begin(1) != thing.skip_begin(2),或者您可以排除它,以便 thing.skip_begin(1 ) == thing.skip_begin(2)++(++(thing.skip_begin(1))) == ++(thing.skip_begin(2))。我认为只要有记录就可以了,或者你可以记录比较它们是 UB 除非它们来自相同的起点。

Standard containers support two iteration policies with two iterator types: ::iterator and ::reverse_iterator. You can convert between the two using the constructor of std::reverse_iterator, and its member function base().

Depending how similar your iteration policies are, it may or may not be easy to provide conversions to different iterator types. The idea is that the result should point at the "equivalent position" in the iteration policy of the destination type. For reverse iterators, this equivalence is defined by saying that if you insert at that point, the result is the same. So if rit is a reverse iterator, vec.insert(rit.base(), ...) inserts an element "before" rit in the reverse iteration, that is to say after the element pointed to by rit in the container. This is quite fiddly, and will only get worse when the iteration policies are completely unrelated. But if all of your iterator types are (or can be made to look like) wrappers around the "normal" iterator that goes over all elements, then you can define conversions in terms of that underlying iterator position.

You only actually need conversions if there are member functions that add or remove elements of the container, because you probably don't want to have to provide a separate overload for each iterator type (just like standard containers don't define insert and erase for reverse iterators). If iterators are used solely to point at elements, then most likely you can do without them.

If the different iteration policies are all iterating in the normal order over a subset of the elements, then look at boost::filter_iterator.

I probably would get some cases where it1==it2 but ++it1!=++it2. I am
not sure if this is allowed by the STL.

If I understand correctly, you got it1 by starting at thing.normal_begin(), and you got it2 by starting at thing.other_policy_begin(). The standard containers don't define the result of comparing iterators of the same type that belong to different ranges, so if you did use a common type, then I think this would be fine provided the documentation makes it clear that although operator== does happen to work, the ranges are separate according to where the iterator came from.

For example, you could have a skip_iterator which takes as a constructor parameter the number of steps it should move forward each time ++ is called. Then you could either include that integer in the comparison, so that thing.skip_begin(1) != thing.skip_begin(2), or you could exclude it so that thing.skip_begin(1) == thing.skip_begin(2) but ++(++(thing.skip_begin(1))) == ++(thing.skip_begin(2)). I think either is fine provided it's documented, or you could document that comparing them is UB unless they came from the same starting point.

命硬 2024-12-28 11:40:45

为什么更多类型会成为问题?它并不一定意味着更多的代码。例如,您可以使您的迭代器类型模板采用迭代策略作为模板参数。然后,迭代策略可以提供迭代的实现:

struct iterate_all_policy {
    iterate_all_policy(iterator<iterate_all_policy> & it) : it(it) {}

    void advance() { /* implement specific advance here */ }
private:
    iterator<iterate_all_policy> & it;
}

您可能必须使迭代策略类成为迭代器类型的朋友。

Why are more types a problem? It does not necessarily mean much more code. For instance, you could make you iterator-type a template that takes an iteration-policy as template-parameter. The iteration-policy could then provide the implementation of the iteration:

struct iterate_all_policy {
    iterate_all_policy(iterator<iterate_all_policy> & it) : it(it) {}

    void advance() { /* implement specific advance here */ }
private:
    iterator<iterate_all_policy> & it;
}

You will probably have to make the iteration-policy-classes friends of the iterator-types.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文