Search code examples
c++stl

Intuition behind the reverse iterator addressing adjustment


Trying to develop an intuition with addressing vector elements with indexes through forward and backward iterators. Since, for example per en.cppreference.com,

“For a reverse iterator r constructed from an iterator i, the relationship &r == &(i - 1) is always true”

is it obvious that in order to keep it_backward pointing to the same element as it_backward I have to compensate this with, subtracting one from the resulting backward iterator:

    std::vector i1{ 0,1,2,3,4 };
    const int element_offset=3;
    auto it_forward = i1.begin()+element_offset;
    auto it_backward = std::make_reverse_iterator(it_forward)-1;

    std::cout << std::endl << "*(it_forward) = " << *(it_forward);
    std::cout << std::endl << "*(it_backward) = "  << *(it_backward);

to be more precise it would be better to use:

    auto it_backward = std::prev(std::make_reverse_iterator(it_forward));

At the same time when we just reverse the range, this is done transparently for programmer and we don’t need to adjust the iterators and I see why.

    std::copy(std::make_reverse_iterator(i1.end()),
              std::make_reverse_iterator(i1.begin()),
              std::ostream_iterator<int>(std::cout, ", "));

My questions is, since these ways of addressing (ranges, elements, ranges limited by elements) sometimes used together, what is the best way to develop an intuition, when we have to adjust the backward iterator and when we don’t?

I can do this every time double checking myself and code, but maybe there is some kind of a good rule of thumb which could help to see when to adjust and when not to?


Solution

  • Pairs of iterators denote half-open ranges. The first iterator is dereferenceable until it is equal to the second.

    If you required passing an iterator to the element you desired to std::reverse_iterator, then you couldn't use end for the start, nor could you use the begin of an empty range. Also there is no iterator value that corresponds to the past-the-end of the reversed range.

    What you should avoid thinking about is constructing a reverse_iterator to a particular element, and instead construct pairs of reverse iterators that denote the range of elements you want, having swapped the begin and end. I.e. instead of your example

    auto it_forward = i1.begin()+element_offset;
    auto it_backward = std::make_reverse_iterator(it_forward)-1;
    

    You would have

    auto it_forward_begin = i1.begin()+element_offset;
    auto it_forward_end = i1.begin()+element_offset+1;
    auto it_backward_begin = std::make_reverse_iterator(it_forward_end);
    auto it_backward_end = std::make_reverse_iterator(it_forward_begin);
    

    Now you can simplify that, inlining the end iterator

    auto it_backward = std::make_reverse_iterator(it_forward+1);
    

    or

    auto it_backward = std::make_reverse_iterator(std::next(it_forward));