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
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.