67

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.

ledokol
  • 673
  • 1
  • 5
  • 5
  • Not all iterators are bidirectional. Those which are bidirectional should always support decrementing an iterator, even the one at the end. – edA-qa mort-ora-y Mar 16 '11 at 07:35
  • @ledokol: as long as there is more than one element in `l`... If the operation `--` is not possible for the given iterator (which depends on the container) you'll get a compile-time error. – Matthieu M. Mar 16 '11 at 08:39
  • @Matthieu "as long as there is more than one element" : isn't it "as long as there is at least one element"? Since end() points beyond the last element, won't `end()-1` piont at a valid element, even in a single-element container? – Robᵩ Mar 16 '11 at 21:00
  • @Rob: it will, I used more to mean `>=` here, isn't it appropriate ? – Matthieu M. Mar 17 '11 at 07:13
  • @Matthieu : "More" means greater or additional. "More than" expresses `>`. "At least 1" or "1 or more" express `>=`. – Robᵩ Mar 17 '11 at 14:09
  • @Rob: thanks, I'll try to remember it for future discussions :) – Matthieu M. Mar 17 '11 at 15:09
  • Why not just code clearly: std::list::reverse_iterator rit =l.rbegin(); Do you really need element before end? Can you not iterate in reverse? – Red.Wave Dec 28 '18 at 07:01

3 Answers3

55

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.

In silico
  • 51,091
  • 10
  • 150
  • 143
  • 1
    What if the list is empty? What would dereference result in? – Nawaz Mar 16 '11 at 07:36
  • 1
    decrementing the end and incrementing begin of empty list will result UB – Hovhannes Grigoryan Mar 16 '11 at 07:38
  • 1
    +1 for the awesome rendering of the table. Yeah, and also for the correct answer. :) – avakar Mar 16 '11 at 07:42
  • 2
    I don't see anything here that suggests I can dereference the iterator returned by `end()`. You probably mean the decremented iterator can be dereferenced (otherwise, what would be the point of decrementing it in the first place)? – visitor Mar 16 '11 at 09:58
  • You only show what one implementation does, which isn't proof that it works for other implementations. Also, an implementation can use code that isn't standard compliant, but happens to work for the specific compiler. An earlier version of VC used a pointer for vector::interator - then it **didn't** work! – Bo Persson Mar 16 '11 at 10:53
  • The table's "operational semantics" shows that it should work "as-if" the code was ok, not that this is how an implementation should do it. Code in the standard document is generally not to be taken literally, it just shows **what** an implementation should do, not **how**. – Bo Persson Mar 16 '11 at 10:55
  • @visitor: Oops, that was a typo. @Bo Persson: C++ Standard 17.3.1.2 says that "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 `*--.a.end()` is equivalent `a.back()` and vice-versa for `vector`, `list` and `deque`, however the implementation does it. – In silico Mar 16 '11 at 18:06
  • No, the code isn't to be taken literally. The given equivalence for a.back() just doesn't work and has been changed in later revisions [here](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#355). – Bo Persson Mar 16 '11 at 21:41
  • @Bo Persson: Ah, so the real issue is that my copy of the C++ standard has a defect. Still, the suggested change is `{ iterator tmp = a.end(); --tmp; return *tmp; }`, so you can still decrement an `end()` iterator and dereference the decremented iterator (as long as it's done on a non-temporary iterator). – In silico Mar 16 '11 at 21:53
  • Right. To show that hard work pays off, I'll change my down vote to an up vote. :-) – Bo Persson Mar 16 '11 at 22:20
  • It is well known that the requirement tables for STL types in the standard were badly written: for example `--end()` looks like C++, but it's really pseudo-code. The "equivalence" in "_specification of **equivalence** of a construct to another construct_" was not to be taken really seriously. – curiousguy Sep 30 '11 at 01:37
11

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; };

Izana
  • 2,537
  • 27
  • 33
2

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.

Hovhannes Grigoryan
  • 1,151
  • 1
  • 8
  • 11
  • 4
    Of course I mean the usage of such code in a context where container emptiness is not possible. – ledokol Mar 16 '11 at 07:32