13

To support STL's notion of half-open ranges, we are allowed to point one-past-the-end of an array. Suppose we have a vector of three elements. If std::vector::iterator is implemented as a pointer, as is usually the case in release builds, then begin and end point to these locations:

    +---+---+---+....
    |   |   |   |   .
    +---+---+---+....
      ^           ^
    begin        end

Where the dots denote the one-past-the-end pseudo-element. Since there is no such thing as a one-before-the-beginning, where exactly would rend point to? Let me illustrate:

    +---+---+---+....
    |   |   |   |   .
    +---+---+---+....
  ^           ^
rend       rbegin

Clearly, the illustration is wrong, because rend is an illegal pointer. So I guess the implementation of std::vector::reverse_iterator can never be a pointer, even in release builds.

Am I right? What would be the most efficient way of implementing reverse_iterator then?

fredoverflow
  • 256,549
  • 94
  • 388
  • 662

5 Answers5

6

The result of rbegin points to the same as end (one past the end), and the result of rend to the same as begin (the first item). When a reverse iterator is dereferenced, it returns a reference to the previous item in the range.

UncleBens
  • 40,819
  • 6
  • 57
  • 90
5

Because you're not permitted to dereference an iterator that points outside the container, it doesn't actually matter what rend() "points" to. It doesn't have to be a legal pointer value, it can be any value that has a particular meaning to the container/iterator type.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 2
    In C, if a pointer doesn't point to an object or one past the last element of an object, and that pointer gets evaluated( not dereferenced ) the behavior is undefined. This question is about C++, but I doubt there is any difference there. I think you should clarify what do you mean by: points outside the container. – this Oct 31 '15 at 22:13
  • 1
    @this: Keep in mind that what you say is true about *pointers*, but not necessarily about *iterators*. Iterators are not always pointers. – Greg Hewgill Oct 31 '15 at 23:10
  • The problem, by my understanding, is that it doesn't even matter if you don't dereference. We're not even allowed to perform pointer arithmetic with, or arithmetic that evaluates to, a pointer 'one-before-the-beginning'. So we can't just create a pointer that points before the beginning of an array and say it's OK as long as we don't dereference it, because the act of creating it, or using it to measure size, or etc is UB. Or am I wrong? – underscore_d May 25 '17 at 19:42
  • @underscore_d: You're thinking about the rules for actual *pointers*. On the other hand, `rend()` returns an *iterator* and the standard library implements iterators in such a way that it avoids undefined behaviour caused by certain kinds of pointer manipulation. – Greg Hewgill May 25 '17 at 20:02
  • @GregHewgill Right, I see what you meant now. I was reading it as "It doesn't have to be a **legal** pointer value", as if that meant it could be an illegal pointer value so long as it wasn't dereferenced... but it sounds like what you meant was that it doesn't have to be **any** pointer value, because it needn't be a pointer at all. :-) Which makes sense, and is illustrated in the other answers. (Plus, as jcoder said, compiler and stdlib authors can collude on all kinds of shenanigans that exploit off-the-books, implementation-defined behaviour, so long as users don't have to see any of it.) – underscore_d May 25 '17 at 21:25
4

There is a difference between what a reverse_iterator points to logically and what its contained iterator points to. Logically, rbegin yields an iterator that points to the last element of the sequence and rend yields an iterator that points to one element before the start. But this is usually implemented with a base iterator which points to the next location after the location the reverse iterator is pointing to. Something like this:

template<class Iter>
class reverse_iter
{
    Iter base;
public:
    explicit reverse_iter(Iter it) : base(it) {}

    reference operator*() const {
        Iter tmp = base;
        --tmp;
        return *tmp;
    }

    reverse_iter& operator++() {--base; return *this;}
};

So, if you initialize such a reverse_iter<> object with container.end(), the base iterator points one past the end, but dereferencing the reverse iterator will give you the last element. No harm done.

Kyle
  • 1,070
  • 2
  • 13
  • 23
sellibitze
  • 27,611
  • 3
  • 75
  • 95
3

The std::reverse_iterator interface includes a .base member function that retrieves an iterator equal to the original. I suspect that what they usually do is just cache the original iterator, and offset by 1 in the operator* overload.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
1

The other answers answer the question well.

But I also wonder if it would be legal anyway, as presumably an implementation can do whatever it likes behind the scenes as long as it works and meets the standards. If it chooses to implement the iterator as a pointer and chooses to dereference that then it's the compilers responsibility to know that that will work. You wouldn't be able to implement it this way yourself but it seems to me that the compiler author has special license to do this as they know what the undefined behavour will be, and don't expose that behavour to you directly, just through an iterator interface.

jcoder
  • 29,554
  • 19
  • 87
  • 130
  • It's legal. Compiler vendors are allowed to take advantage of their own UB, and do so all the time. A common example is the `offset_of` macro, which often is implemented in terms of arithmetic on a null pointer. – MSalters Dec 06 '10 at 10:33