0

There is something that I can't quite get my head around with bidirectional iterators.

Going forward through the collection is ok:

auto it = set.begin();
while (it != set.end())
{
  // Do something
  it++;
}

end() is the element after the last element. So let's say I want to go in reverse using the same iterator from the end:

if(it == set.end())
{
    it--; // Now points to last element
}
while(it != set.begin())
{
  // Do something
  it--;
}

But this last loop is flawed because begin() is the first element and we want to include it in processing. So how do we iterate back to (and including) the first element with a bidirectional iterator?


To provide context ...

The user iterates forward one by one. So they might be on the end() or any element before the end. Then, they decide they want to go backwards. Thus the sanity check since if we are at the end we must decrement by one. Now the user goes backwards one by one. Until they reach the first element.

I asked a similar question yesterday but deleted it since I did not grasp some of the concepts which I now have corrected for forward iteration.

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • 1
    This process seems a bit confused. At the start, does `it` point to the first element to process in reverse order, or does it point to the element *past* the first element to be processed? Your `if` statement suggests that sometimes it points past that element (ie: is the end iterator) and sometimes it doesn't. It should be consistent. – Nicol Bolas Jul 05 '23 at 15:13
  • 6
    That is why there is `rbegin()` and `rend()`... – paleonix Jul 05 '23 at 15:15
  • I would disagree, what OP wants to do can be more easily done without reverse iterators – maxplus Jul 05 '23 at 15:17
  • 1
    Since you tagged question with [tag:c++20] you can use ranges: [std::ranges::reverse_view](https://en.cppreference.com/w/cpp/ranges/reverse_view). – Marek R Jul 05 '23 at 15:17
  • Did you try `auto iter=set.end(); while (iter != set.begin()) { --iter; /* do stuff here */}`? – Sam Varshavchik Jul 05 '23 at 15:17
  • @paleonix So are you saying that even with a bidirectional iterator I can still use `rend`? I have only used used that specifically with a `reverse_iterator`. – Andrew Truckle Jul 05 '23 at 15:22
  • 1
    @AndrewTruckle: "*So they might be on the end() or any element before the end.*" That doesn't answer my question: is the element they stopped on an element they want *processed* or not? By the time your reverse code sees the iterator, the answer to this question needs to be consistent. – Nicol Bolas Jul 05 '23 at 15:24
  • @NicolBolas The iterator is in the class itself. For going in both directions of the collection. The user can go forward or backward. I simply shows all code as a snippet of how I understand to iterate a bidirectional iterator backwards to start. – Andrew Truckle Jul 05 '23 at 15:25
  • 1
    @maxplus is right that in this particular situation it isn't ideal because you can't compare your bidirectional iterator to a reverse iterator for inequality. One would need to construct a new range between `begin()` and `it` and reverse that. – paleonix Jul 05 '23 at 15:39
  • @maxplus `x` -1 - the way you said it makes sense. I would want to begin with the previous name from the one they are on. – Andrew Truckle Jul 05 '23 at 15:40
  • 1
    Then you need to get rid of the conditional in the question. – paleonix Jul 05 '23 at 15:41
  • @paleonix OK, so I just use `--it`. But if `it` is pointing to `end()` it is the element after the last. So I would have to go back two. Confusing myself I guess! – Andrew Truckle Jul 05 '23 at 15:42
  • 1
    Extrapolating from what you said elsewhere, you are actually interested into the element that is pointed at by `it` and in that case you just want a completely different code path if `it == set.end()` (nothing was found/selected) that may not iterate back but e.g. throw an exception. That is (I think) @NicolBolas' point. – paleonix Jul 05 '23 at 15:48
  • 1
    @AndrewTruckle, if you ask you main question again (this one is concerned with a simple, not nested structure of a container, and is IMO completely answered by Nicol) I'll post the solution code (minus GUI) for you (at least if I understood what your end goal is correctly from your previous question), since it's a practical problem and I think you've spent enough time trying to figure it out. Maybe it will help you understand what happens and how to implement similar functionality next time. – maxplus Jul 05 '23 at 16:07
  • @maxplus Very kind of you! Please see: https://stackoverflow.com/q/76622625/2287576 – Andrew Truckle Jul 05 '23 at 17:15

3 Answers3

1

It is easy to do, for example:

while(it != set.begin())
{
  // Do something with --it
}

Here is a demonstration program:

#include <iostream>
#include <set>
#include <iterator>

int main( 
{
    std::set<int> set = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    auto it = std::cbegin( set );

    while ( it != std::cend( set ) )
    {
        std::cout << *it++ << ' ';
    }
    std::cout << '\n';

    while ( it != std::cbegin( set ) )
    {
        std::cout << *--it << ' ';
    }
    std::cout << '\n';
}

The program output is:

1 2 3 4 5 6 7 8 9 10 
10 9 8 7 6 5 4 3 2 1 

That is after the first while loop the iterator is equal to the iterator std::end( set ). It does not equal to std::begin( s ).

So in the second while loop you may write the condition:

while ( it != std::begin( set ) )

and within the loop the iterator is decremented to point to a valid element.

Just consider a set that contains only one element, for example:

    std::set<int> set = { 1 };

and investigate the program.

I is the same as for example to output a string in the reverse order using indices.

const char *s = "Hello World!";

for ( size_t n = std::strlen( s ); n != 0; )
{
    std::cout << s[--i];
}
std::cout << '\n';
halfer
  • 19,824
  • 17
  • 99
  • 186
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
1

Before starting this process, you need to be clear on what it is supposed to be. Your if statement suggests that you're not clear on that.

If it is supposed to point at the first element to be processed in the sequence, then the only way it will ever be the end element is if the list is empty (ie: end and begin are the same). In that case, decrementing the iterator is wrong.

To handle this case, your code should look like this:

if(it != set.end())
{
  for(; it != set.begin(); --it)
  {
    //Do stuff with *it.
  }
}

If it is supposed to point to the element after the first element to process, then your code would look like this:

while(it != set.begin())
{
  --it;
  //Do stuff with *it
}

The user iterates forward one by one. So they might be on the end() or any element before the end. Then, they decide they want to go backwards.

That user needs to figure out what they mean. When they provide the iterator to the code doing reverse processing, they are the ones who need to be clear on what the value of that iterator means. It either is always part of the range, or it never is part of the range.

It shouldn't be "it's part of the range, unless its the end iterator, then it isn't part of the range".

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Thanks. This contain is a set of names in a grid (of more names). User iterates the container and it jumps to each name in the grid. I have no idea at what point the user will stop iterating in a forward direction. They might keep going to the last name. They may decide (at any given point) that they want to go backwards from where they are back through the names. So I an unsure of why this is confusing. I know exactly the scenario but I have to cover the principle of letting the user manually walk one by one on demand (using the single instance of the iterator in the class) forward/backward. – Andrew Truckle Jul 05 '23 at 15:33
  • @AndrewTruckle: "*So I an unsure of why this is confusing.*" What you describe is a user that has no idea what they're doing (or *you* have no idea what the purpose of iterating through that list is). Are they looking for something? Does the "reverse" point represent the place where they found what they're looking for? The whole thing doesn't make any kind of sense; you describe in such generalities that it isn't clear the "user" in question knows what they want. – Nicol Bolas Jul 05 '23 at 15:34
  • The user is trying to decide which name they want to use (for scheduling). The container has a list of suggested names. The user walks through those names, seeing them in the grid which has extra info. I think we are going off track. But thank you for your answer. Much appreciated. – Andrew Truckle Jul 05 '23 at 15:37
  • 2
    @AndrewTruckle: So the user is searching for a name. So either the user found one or they didn't. These are two different circumstances which merit *two different responses*. – Nicol Bolas Jul 05 '23 at 15:38
  • 1
    @AndrewTruckle: Put another way, consider this reversing code to be a cleanup routine, `do_cleanup` that takes an iterator. If your code found a name, it should call `do_cleanup(it++)`, which means that the name it found should be included in the cleanup. If your code *didn't* find a name, it should call `do_cleanup(it)`. These are separate circumstances and therefore have different cleanup requirements. It's the code providing the iterator that needs to conform to the interface, not the cleanup routine itself. – Nicol Bolas Jul 05 '23 at 15:52
1

The correct way to efficiently traverse a range in reverse is:

auto it = v.end();
while (it != v.begin()) {
    --it;
    use(*it);
} 

If v is empty, then the loop is never entered, since v.end() == v.begin().

If v contains elements, then initially v.end() points one past the end, so we first decrement it (now pointing to the last element) before using it. The first call to use will be the last element.

When we decrement all the way to the first element, such that use(*it) is using the first element, that means that it == v.begin(), so we don't enter the loop again.

This is notably different from the structure of a for loop, since we decrement at the beginning of the body rather than at the end.

In C++20, you can simply avoid thinking about how to structure the loop entirely by writing:

for (auto elem : std::views::reverse(v)) {
    use(elem);
}

Which basically does:

auto it = std::make_reverse_iterator(v.end());
auto end = std::make_reverse_iterator(v.begin());
for (; it != end; ++it) {
    use(*it);
}

Which desugars roughly into:

auto it = v.end();
auto end = v.begin();
for (; it != end; --it) {
    auto this_elem = std::prev(it);
    use(*this_elem);
}

Note that this two decrements per element instead of one in order to give you the convenient syntax. This usually doesn't matter for most problems, and the convenient syntax is trivial to write correctly, whereas you (or, at least, I) really have to think a bit to write the correct loop reversal syntax correctly (i.e. not either run past or skip the first element).

Barry
  • 286,269
  • 29
  • 621
  • 977