11

That is, providing that container is not empty, can I safely do this:

std::vector<int> container;
container.push_back( 0xFACE8D );
auto last = container.end() - 1;

and this:

EDIT: replaced -1 with -- here:

std::list<int> container;
container.insert( 0xFACE8D );
auto last = container.end();
--last;

and again for arbitrary non-empty container?

EDIT: let me clarify the question.

Sometimes perfectly legal code behaves incorrectly. The question is: assuming that the above code compiles, is it safe to do something like that?

It should be safe for ordinary C-style arrays because the corresponding iterators are just pointers. But is it safe for more complicated containers?

Suppose that one implements list with iterators like this one:

class MyListIterator {
    MyListIterator *prev, *next;
    MyListIterator * operator--() { return prev; }
    ...
};

class MyList {
    MyListIterator *end() { return NULL; }
    ...
}; 

Then an attempt to decrement container::end(), despite being perfectly legal syntactically, would cause a segfault.

I hope, though, that stl containers are much smarter than that. Thus the question on the guarantees on the above stl::list code behavior, if any.

Michael
  • 5,775
  • 2
  • 34
  • 53
  • 1
    If it compiles, that means somebody went to the trouble of implementing `operator-()`, which strongly suggests they intended for you to call it... – Kevin Oct 01 '15 at 23:06
  • 1
    @crayzeewulf: I'm not advocating that usage, just curious whether it's safe, have seeing something like that in the code. – Michael Oct 01 '15 at 23:07
  • 2
    @Kevin "It compiles so it must be okay" is a rather poor heuristic in C++. – John Kugelman Oct 01 '15 at 23:07
  • @Michael Got it. I realized it after I saw `std::list` in the second case. – crayzeewulf Oct 01 '15 at 23:07
  • @Kevin: if everything that compiles would work correctly I would be a much happier person. – Michael Oct 01 '15 at 23:08
  • 1
    If your compiler accepts this with std::list, I will be quite surprised. – Kevin Oct 01 '15 at 23:18
  • Which vindicates my comment: now that your code compiles, it is also correct. – Kevin Oct 01 '15 at 23:28
  • http://stackoverflow.com/questions/12057046/decrementing-an-off-the-end-iterator – Marco A. Oct 01 '15 at 23:44
  • That would not be a conformant implementation of `std:list`. In a conformant implementation you can decrement the iterator returned by `end` as long as it is not the same iterator as is returned by `begin` -- that is, as long as the list isn't empty. – rici Oct 02 '15 at 00:08
  • That's the reason why all node-based standard containers (list, (unordered)map/set) have allocating (and thus throwing) default constructors - they must allocate sentinel nodes to allow this kind of things - like going one step back from `end()` – Ilya Popov Oct 02 '15 at 08:29

2 Answers2

10

std::vector returns random-access iterators, so yes, this is safe with vector.

std::list returns bidirectional iterators. They can be incremented (++) and decremented (--), but don't allow for arbitrary arithmetic (+, -) or relative comparisons (>, <). It's not safe with list.

You can use std::advance on bidirectional iterators to advance or rewind arbitrary amounts. In C++11 you can use std::prev in place of iter - 1.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
3

The C++ standard says that std::vector<T>::iterator is random access iterator pointing to the past-the-end element. So your can safely use - and + operators.

Unlike std::vector, std::list<T>::iterator is bi-directional iterator that support only -- and ++ operators.

Andrey Nasonov
  • 2,619
  • 12
  • 26