Is it safe to invoke std::map::erase with std::map::begin? - c++

We (all) know, erasing an element, pointer by an iterator invalidates the iterator, for example:
std::map< .. > map_;
std::map< .. >::iterator iter;
// ..
map_.erase( iter ); // this will invalidate `iter`.
But, what about:
map_.erase( map_.begin() );
is this safe? Will map_.begin() be a valid iterator, pointing to the (new) first element of the map?
"test it" is not a solution.

begin() is not an iterator but returns an iterator. After erasing the first element, begin()returns another (valid) iterator.
std::map<int, int> m;
m[1] = 2;
m[2] = 3;
m.erase(m.begin()); // <- begin() points to 1:2
std::cout << m.begin()->second; // <- begin() points to 2:3 now

On cppreference, we see:
All iterators (pos, first, last) must be valid and dereferenceable,
that is, the end() iterator (which is valid, but is not
dereferencable) cannot be used.
That pretty much answers your question. As long as the iterator returned by begin() is valid and dereferencable, it is OK to be used in std::map::erase(). A good way then to check if begin() is OK to be used in std::map::erase is by checking it if it is not equal to end():
if(map.begin() != map.end()) {
map.erase(map.begin());
}
Alternatively, you could also check if the map is empty, and use std::map::erase if it isn't
if(!map.empty()) {
map.erase(map.begin());
}

Of course it is.
map::begin returns a valid iterator referring to the first element in the map container.
http://www.cplusplus.com/reference/map/map/begin/
Pay attention to empty map.

is this safe?
Yes. It invalidates the temporary iterator returned by this call to begin(), and that iterator is destroyed at the end of the statement.
Will map_.begin() be a valid iterator, pointing to the (new) first element of the map?
Yes, unless the map is now empty. Erasing an element does not prevent you from creating new iterators to the remaining elements; that would make the map unusable.

Related

Erasing while traversing in C++ stl map giving runtime error

Following lines of C++ code gives runtime error but if erase operation mymap.erase(v) is removed it works:
map<int,int> mymap = {{1,0},{2,1},{9,2},{10,3},{11,4}};
for(auto it=mymap.rbegin();it!=mymap.rend();){
int v=it->first;
++it;
mymap.erase(v);
}
demo
Here iterator it is changed before deleting its value v, so iterator it should remain unaffected I believe.
When you are calling erase(v), you are invalidating the base iterator that the next reverse_iterator (from ++it) is using. So you need to create a new reverse_iterator from the base iterator that precedes the erased value.
Also, rather than erasing the value that the reverse_iterator is referring to, you should erase the base iterator instead, since you already know which element you want to erase. There is no need to make the map go hunting for the value again.
This works for me:
map<int,int> mymap = {{1,0},{2,1},{9,2},{10,3},{11,4}};
for(auto it = mymap.rbegin(); it != mymap.rend(); ){
auto v = --(it.base());
v = mymap.erase(v);
it = map<int,int>::reverse_iterator(v);
}
Demo
On the other hand, this loop is essentially just erase()'ing all elements from mymap, so a better option is to use mymap.clear() instead.
Indeed, std::map::erase:
References and iterators to the erased elements are invalidated. Other references and iterators are not affected.
But std::reverse_iterator
For a reverse iterator r constructed from an iterator i, the relationship &*r == &*(i-1) is always true (as long as r is dereferenceable); thus a reverse iterator constructed from a one-past-the-end iterator dereferences to the last element in a sequence.
Perhaps it gets more clear when you look at the image on the cppreference page.
The crucial part is "Reverse iterator stores an iterator to the next element than the one it actually refers to".
As a consequence (small modification to your code)
auto& element = *it; // fails in the next iteration, because...
int v = element.first;
++it; // now it stores an iterator to element
mymap.erase(v); // iterators to element are invalidated
You are erasing the element that is used by it in the next iteration.

c++ is there any possibility that `map.end()` can change during the life time of a `std::map`

just wondering if I have a map declared like this,
map<int, int> my_map;
and assign end() iterator to a variable, and never change,
auto end_it = my_map.end()
Then later I will change my_map by having many erase and insert of my_map,
will the below always stays true during the life time of my_map?
end_it == my_map.end()
std::map::insert guarantees:
No iterators or references are invalidated.
std::map::erase guarantees:
References and iterators to the erased elements are invalidated. Other references and iterators are not affected.
Therefore the end iterator should stay valid. If you use any other operation, you should check whether it might invalidate the iterators.

Iterator validity with ordered maps

I have a map definition and subsequent manipulation like this.
map<int,string> m;
m.insert(std::pair<int,string>(1,"A");
m.insert(std::pair<int,string>(2,"B");
m.insert(std::pair<int,string>(3,"C");
m.insert(std::pair<int,string>(4,"D");
auto it = m.find(2);
m.erase(m.find(3));
cout<< it->second;
Will "it" be valid after an erase to some other element ?
Will "it" be valid after an erase to some other element ?
Yes, std::map::erase will only invalidate references and iterators to the erased elements.
References and iterators to the erased elements are invalidated. Other references and iterators are not affected.
Note the code m.erase(m.find(3)); has a potential problem, since std::map::find will return end() iterator if nothing is found, but end() iterator cannot be used with std::map::erase.
The iterator pos must be valid and dereferenceable. Thus the end()
iterator (which is valid, but is not dereferencable) cannot be used as
a value for pos.
Yes it will. Only the erased iterator is invalidated when a map erase is performed.

Got singular iterator error in looping with iterator and pop_back

Giving the below code (say it's named deque.cpp)
#include <cstdio>
#include <deque>
int main()
{
std::deque<int> d = {1, 2, 3};
for (auto it = d.rbegin(); it != d.rend();) {
printf("it: %d\n", *it);
++it;
d.pop_back();
}
return 0;
}
Compile with g++ -std=c++11 -o deque deque.cpp, it runs well:
$ ./deque
it: 3
it: 2
it: 1
But if compile with -D_GLIBCXX_DEBUG (g++ -std=c++11 -o deque_debug deque.cpp -D_GLIBCXX_DEBUG, it gets below error:
$ ./deque_debug
it: 3
/usr/include/c++/4.8/debug/safe_iterator.h:171:error: attempt to copy-
construct an iterator from a singular iterator.
...
It looks like the second loop's ++it is constructing from an singular iterator.
But I think after the first loop's ++it, the iterator points to 2, and pop_back() should not invalidate it. Then why the error occurs?
Note: I know the code could be re-write as below:
while (!d.empty()) {
auto it = d.rbegin();
printf("it: %d\n", *it);
d.pop_back();
}
And the error will be gone.
But I do want to know what exactly happens on the error code. (Does it mean the reverse iterator dose not actually point to the node I expect, but the one after it?)
Update: #Barry's answer resolved the question.
Please let me put an extra related question: the code
for (auto it = d.rbegin(); it != d.rend();) {
printf("it: %d\n", *it);
d.pop_back();
++it; // <== moved below pop_back()
}
is expected to be wrong, where ++it should be operating on an invalidated iterator. But why the code does not cause error?
The problem here arises from what reverse iterators actually are. The relevant relationship for a reverse iterator is:
For a reverse iterator r constructed from an iterator i, the relationship &*r == &*(i-1) is always true (as long as r is dereferenceable); thus a reverse iterator constructed from a one-past-the-end iterator dereferences to the last element in a sequence.
When we then do std::deque::pop_back(), we invalidate:
Iterators and references to the erased element are invalidated. The past-the-end iterator is also invalidated. Other references and iterators are not affected.
rbegin() is constructed from end(). After we increment it the first time, it will dereference to the 2 but its underlying base iterator is pointing to the 3 - that's the erased element. So the iterators referring to it include your now-advanced reverse iterator. That's why it's invalidated and that's why you're seeing the error that you're seeing.
Reverse iterators are complicated.
Instead of incrementing it, you could reassign it to rbegin():
for (auto it = d.rbegin(); it != d.rend();) {
printf("it: %d\n", *it);
d.pop_back();
// 'it' and 'it+1' are both invalidated
it = d.rbegin();
}
Erasing from the underlying container invalidates an iterator. Quoting from the rules:
Iterators are not dereferenceable if
they are past-the-end iterators
(including pointers past the end of an array) or before-begin
iterators. Such iterators may be dereferenceable in a particular
implementation, but the library never assumes that they are.
they are singular iterators, that is, iterators that are not associated with any sequence. A null pointer, as well as a default-constructed pointer
(holding an indeterminate value) is singular
they were invalidated by
one of the iterator-invalidating operations on the sequence to which
they refer.
Your code causes the iterator to be invalidated by the pop_back operation, and thus as per the third point above, it becomes non dereferenceable.
In your while loop you're avoiding this problem by getting a (new) valid iterator in each loop repetition.

Will the erase function of set in C++ change the address of other elements?

I have the following code:
set<Key> test;
test.insert(key1);
test.insert(key2);
iter1 = test.find(key1);
iter2 = test.find(key2);
test.erase(iter1);
My question is, if key1 is deleted, now can I use iter2 to refer to key2 in test?
Thanks
Yes, set's erase invalidates only iterators that point to the element that was erased (note that this is not necessarily true for all containers).
Asociative containers set, multiset, map and multimap are required to only invalidate iterators and references to the erased elements.
In a deque all the iterators and references are invalidated, unless the erased members are at an end (front or back) of the deque (23.2.1.3/4),
in a list only the iterators and references to the erased element is invalidated (23.2.2.3/3) and on a vector every iterator and reference after the point of erase is invalidated (23.2.4.3/3)
Strictly speaking you have to check the return value of the "insert" operation and ensure that key1 and key2 don't compare equal; otherwise iter1 == iter2 and erasing iter1 invalidates iter2. But in general see the previous answer, erasing an iterator invalidates only that iterator and no others.
Example:
struct Foo
{
Foo(std::string s = "") : s(s) { }
bool operator<(const Foo & other) { return s.size() < other.size(); }
}
std::set<Foo> x;
x.insert(Foo("Cat"));
x.insert(Foo("Dog")); // booboo