Search code examples
c++11stdset

Reverse iterator is being advanced by std::set::erase


I am trying to erase the element that I just traversed into. I initially forgot to store the return value of s1.erase(... inside the for loop to ultimately set the condition to exit the loop. With the way the code is right now, I expected the loop to continue indefinitely. But it works just the way it was originally intended to work. It looks like std::erase advances the iterator and stores the value in rit. I am not able to find any documentation that explains this behavior.

https://en.cppreference.com/w/cpp/container/set/erase says that the iterator returned has to be stored. All arguments of the set::erase are passed by value, so how is the reverse iterator being advanced?

How is this loop completing?

std::set<int> s1;
s1.insert(20);
s1.insert(30);
s1.insert(50);

auto rit = s1.rbegin();

for (; rit!= s1.rend();)
{
    std::cout << "rit is " << *rit << " size is " << s1.size() << std::endl;
    s1.erase(std::next(rit).base());
    std::cout << "rit after erase is " << *rit << std::endl;
}

The output is

rit is 50 size is 3

rit after erase is 30

rit is 30 size is 2

rit after erase is 20

rit is 20 size is 1

Segmentation fault


Solution

  • Recall that reverse_iterator::base() is always one element behind the apparent iterator's value. For example, after auto rit = s1.rbegin(), *rit returns the last element, while rit.base() == s1.end().

    In other words, *rit == *prev(rit.base())

    In your loop, initially rit.base() == s1.end(). Then std::next(rit).base() refers to the last element; that element is being erased. In std::set, erasing an element only invalidates iterators to that element, but not any others. s1.end() is still a valid iterator, and so is rit, with rit.base() still equal to s1.end(). So on the next iteration of the loop, you erase the last element again, and leave rit.base() == s1.end() again. And so on.

    At some point, the last element is erased, s1 becomes empty, and then *rit exhibits undefined behavior. Recall that *rit == *prev(rit.base()), but there is no previous element anymore.