通用结束迭代器与容器 `end()` 的递减要求

Decrementable requirements of general end iterators vs container `end()`

我正在研究 ReversibleContainer and its associated LegacyRandomAccessIterators。它们包装了一个预先存在的数据结构,代表一个可直接索引的对象集合。我决定让迭代器独立存在,并让默认构造的迭代器代表“结束”:

// constructs an iterator that provides a view of 'data'
the_iterator (thedata *data, difference_type index = 0);

// constructs an iterator representing the end
the_iterator ();

所以我可以做例如:

std::for_each(the_iterator(data), the_iterator(), ...);

迭代器完成所有工作。该容器非常轻巧。我这样实现了容器的 begin()end()

struct the_container {
    the_data *data; // <- object wrapped by this container
    the_iterator begin () { return the_iterator(data); }
    the_iterator end () { return the_iterator(); }
};

我已经让它工作得很好,但在测试中我意识到我搞砸了它不符合基本的 Container 要求,因为:

所以我现在必须修复容器。我想到的解决方案是(让 data->number_of_items 包含项目数):

然后容器会做:

struct the_container {
    the_data *data; // <- object wrapped by this container
    the_iterator begin () { return the_iterator(data, 0); }
    the_iterator end () { return the_iterator(data, data->number_of_items); }
};

现在,很好,它满足所有 Container 要求。但是,我现在想知道是否允许我的默认构造的迭代器存在。

那么,我的问题是:虽然 Container 对迭代器提出了可递减性要求,它 returns 来自 end() , 是否对仅表示某些数据的“结束”但不涉及容器的 end()?

的迭代器有类似的要求

更正式地说,如果:

那么--j是否需要有效并且需要结束指向容器的最后一个元素?在我的案例中,这种情况的一个例子是:

the_container container(data); // <- assume data->number_of_items > 0
the_iterator b = container.begin();
the_iterator e = container.end();
the_iterator j;

assert(container.empty() == false);
assert(e == j);
assert(distance(b, e) == distance(b, j));

-- e;  // <- this is required to be well-defined
-- j;  // <- but is this??

所以,是的,这就是我的问题。我担心例如某些东西或其他东西的某些实现<algorithm> 可能会假设我的“结束”迭代器之一是可递减的,或者我正在破坏一些我不理解的细微之处。

作为一个框架挑战,你这里有一个哨兵。 sentinal 是您可以将迭代器与之进行比较的东西,但它不是迭代器。

激励示例是一种类型,当与 char 指针比较时,它只检查 char 指针的取消引用是否为 null。您可以使用它来编写 C 级“读取字符串直到为空”,同时仍然使用 for(:) 循环等。

扩充了基于范围的 for(:) 循环,允许开始和结束具有不同的类型,以允许哨兵存在并发挥最大效率。

考虑用结束标记替换默认构造的迭代器技巧。这不适用于许多 pre-ranges-v2 算法,但至少您使用的是久经考验的真实模式。

不,我不认为你的结束迭代器违反了规则。例如,来自两个容器的迭代器在标准下可以有无意义的 == 结果。您的结束迭代器可以被视为来自另一个容器的迭代器,具有可预测的 == 行为。

现在,当您将它传递给算法时会发生什么?这可能会使您的程序格式错误;该算法可以检测到您为特征提供的保证,并且您的假结束迭代器不是与开始迭代器位于同一容器中的有效双向迭代器。

Then does --j need to be valid and need to end up pointing to the last element of the container?

对于任何双向迭代器,只要满足其前置条件,--j 就必须定义良好。并且前提条件是存在另一个迭代器 s j == ++s。对于您的容器,最后一个有效元素的迭代器就是这样的 iterator

因此,我认为您的迭代器无法满足双向迭代器的要求,因此容器将不可逆。

如果您想为双向迭代器定义标记,可以使用单独的类型来完成,这样迭代器要求就不会应用到它。需要注意的是,大多数标准算法不支持单独的迭代器和哨兵类型。 std::ranges 算法会。

或者,您可以指定默认的初始化迭代器是 singular 即它不与任何容器相关联。在这种情况下 -- j 不会有任何要求(j == e 也不会)。但是 b, j 将不是一个有效范围,你仍然不能将这样的对传递给标准算法。