结束迭代器递减的可移植性如何?

发布于 2024-10-22 02:02:04 字数 257 浏览 6 评论 0原文

刚刚在我的公司源代码中遇到了 end() 迭代器的递减,这对我来说看起来很奇怪。据我记得这在某些平台上有效,但在其他平台上无效。也许我错了,但是我在标准中找不到任何有用的东西。标准只说 end() 返回一个迭代器,它是最后的值,但它保证是可递减的吗?这样的代码如何符合标准?

std::list<int>::iterator it = --l.end();

提前致谢。

Just encountered decrement of end() iterator in my company source codes and it looks strange for me. As far as I remember this was working on some platforms, but not for the others. Maybe I'm wrong, however I couldn't find anything useful in standard about that. Standard only says that end() returns an iterator which is the past-the-end value, but is it guaranteed to be decrementable? How does code like that match the standard?

std::list<int>::iterator it = --l.end();

Thanks in advance.

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

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

发布评论

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

评论(3

哽咽笑 2024-10-29 02:02:04

我认为这是相关条款:

ISO/IEC 14882:2003 C++ 标准 23.1.1/12 – 序列

表 68 列出了序列操作
为某些类型提供的
顺序容器,但不是其他容器。
实施应提供这些
适用于所有容器类型的操作
显示在“容器”列中,并且
应实施这些措施,以便采取
摊销常数时间。

    +----------------------------------------------------------------------------+
    |                                  Table 68                                  |
    +--------------+-----------------+---------------------+---------------------+
    |  expression  |   return type   |     operational     |      container      |
    |              |                 |      semantics      |                     |
    +--------------+-----------------+---------------------+---------------------+
    | a.front()    | reference;      | *a.begin()          | vector, list, deque |
    |              | const_reference |                     |                     |
    |              | for constant a  |                     |                     |
    +--------------+-----------------+---------------------+---------------------+
    | a.back()     | reference;      | *--a.end()          | vector, list, deque |
    |              | const_reference |                     |                     |
    |              | for constant a  |                     |                     |
    ..............................................................................
    .              .                 .                     .                     .
    .              .                 .                     .                     .
    ..............................................................................
    | a.pop_back() | void            | a.erase(--a.end())  | vector, list, deque |
    ..............................................................................
    .              .                 .                     .                     .
    .              .                 .                     .                     .

因此,对于列出的容器,不仅从 end() 返回的迭代器应该是可递减的,递减后的迭代器也应该是可取消引用的。 (当然,除非容器是空的。这会调用未定义的行为。)

事实上,vectorlistdeque 实现随Visual C++ 编译器的执行方式与表中的完全一样。当然,这并不意味着每个编译器都会这样做:

// From VC++'s <list> implementation

reference back()
    {    // return last element of mutable sequence
    return (*(--end()));
    }

const_reference back() const
    {    // return last element of nonmutable sequence
    return (*(--end()));
    }

请注意表中的代码:

ISO/IEC 14882:2003 C++ 标准 17.3.1.2/6 – 要求

在某些情况下,语义
需求以 C++ 形式呈现
代码。 此类代码旨在作为
a 的等价规范
构造到另一个构造
,而不是
必然作为构造方式
必须实施。

因此,虽然实现确实可能无法根据 begin()end() 实现这些表达式,但 C++ 标准指定这两个表达式是等效的。换句话说,根据上述子句,a.back()*--a.end() 是等效的构造。在我看来,这意味着您应该能够将 a.back() 的每个实例替换为 *--a.end() ,反之亦然让代码仍然有效。


根据 Bo Persson 的说法,我手头的 C++ 标准修订版 对于表 68 有缺陷

建议的解决方案:

更改表68中的规格
中的“可选序列操作”
23.1.1/12 的“a.back()”来自

<前><代码>*--a.end()

{ 迭代器 tmp = a.end(); --tmp;返回*tmp; }

以及规格
“a.pop_back()”来自

a.erase(--a.end())

{ 迭代器 tmp = a.end(); --tmp; a.擦除(tmp); }

看来您仍然可以递减从 end() 返回的迭代器并取消引用递减的迭代器,只要它不是临时的。

I think this is the relevant clause:

ISO/IEC 14882:2003 C++ Standard 23.1.1/12 – Sequences

Table 68 lists sequence operations
that are provided for some types of
sequential containers but not others.
An implementation shall provide these
operations for all container types
shown in the "container" column, and
shall implement them so as to take
amortized constant time.

    +----------------------------------------------------------------------------+
    |                                  Table 68                                  |
    +--------------+-----------------+---------------------+---------------------+
    |  expression  |   return type   |     operational     |      container      |
    |              |                 |      semantics      |                     |
    +--------------+-----------------+---------------------+---------------------+
    | a.front()    | reference;      | *a.begin()          | vector, list, deque |
    |              | const_reference |                     |                     |
    |              | for constant a  |                     |                     |
    +--------------+-----------------+---------------------+---------------------+
    | a.back()     | reference;      | *--a.end()          | vector, list, deque |
    |              | const_reference |                     |                     |
    |              | for constant a  |                     |                     |
    ..............................................................................
    .              .                 .                     .                     .
    .              .                 .                     .                     .
    ..............................................................................
    | a.pop_back() | void            | a.erase(--a.end())  | vector, list, deque |
    ..............................................................................
    .              .                 .                     .                     .
    .              .                 .                     .                     .

So for the containers listed, not only should the iterator returned from end() be decrementable, the decremented iterator should also be dereferencable. (Unless the container is empty, of course. That invokes undefined behavior.)

In fact, vector, list and deque implementations that came with the Visual C++ compiler does it exactly like the table. Of course, that's not to imply that every compiler does it like this:

// From VC++'s <list> implementation

reference back()
    {    // return last element of mutable sequence
    return (*(--end()));
    }

const_reference back() const
    {    // return last element of nonmutable sequence
    return (*(--end()));
    }

Note about the code in the table:

ISO/IEC 14882:2003 C++ Standard 17.3.1.2/6 – Requirements

In some cases the semantic
requirements are presented as C + +
code. Such code is intended as a
specification of equivalence of a
construct to another construct
, not
necessarily as the way the construct
must be implemented.

So while it's true that an implementation may not implement those expressions in terms of begin() and end(), the C++ standard specifies that the two expressions are equivalent. In other words, a.back() and *--a.end() are equivalent constructs according to the above clause. It seems to me that it means that you should be able to replace every instance of a.back() with *--a.end() and vice-versa and have the code still work.


According to Bo Persson, the revision of the C++ standard that I have on hand has a defect with respect to Table 68.

Proposed resolution:

Change the specification in table 68
"Optional Sequence Operations" in
23.1.1/12 for "a.back()" from

*--a.end()

to

{ iterator tmp = a.end(); --tmp; return *tmp; }

and the specification for
"a.pop_back()" from

a.erase(--a.end())

to

{ iterator tmp = a.end(); --tmp; a.erase(tmp); }

It appears that you can still decrement the iterator returned from end() and dereference the decremented iterator, as long as it's not a temporary.

是你 2024-10-29 02:02:04

来自 std::prev 的文档

虽然表达式 --c.end() 经常编译,但不能保证这样做:c.end() 是一个右值表达式,并且没有迭代器要求指定保证右值递减工作。特别是,当迭代器实现为指针时,--c.end() 无法编译,而 std::prev(c.end()) 则可以。

这意味着前缀递减操作的实现可能不是内部类形式< code>iterator iterator::operator--(int) 但在类外部重载,形成 iterator operator--(iterator&, int)

所以你应该选择 std::prev 或执行以下操作:
<代码>
{ 自动结束 = a.end(); - 结尾; };

from the documentation for std::prev

Although the expression --c.end() often compiles, it is not guaranteed to do so: c.end() is an rvalue expression, and there is no iterator requirement that specifies that decrement of an rvalue is guaranteed to work. In particular, when iterators are implemented as pointers, --c.end() does not compile, while std::prev(c.end()) does.

which means the implementation for the prefix decrement operation may not be the inside class form iterator iterator::operator--(int) but overloaded outside class form iterator operator--(iterator&, int).

so you should either prefer std::prev or do the following:

{ auto end = a.end(); --end; };

﹉夏雨初晴づ 2024-10-29 02:02:04

这段代码不行,如果列表为空,你就有麻烦了。

因此,检查一下,如果列表不为空,则代码非常好。

This code is not OK, in case list is empty you are in trouble.

So check for that, if list is not empty the code is very fine.

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