Why const_iterator could be used with std::map::erase - c++

I was under the impression one cant use erase on a const iterator. Check this code.
Why does the below code compile (C++11, gcc)?
long getMax(const bool get_new)
{
long max_val=0;
TO now=getNow();
map<TO, long>& m=get_new?m_new:m_old;
for(auto it=m.cbegin(); it !=m.cend())
{
if(now.compareTime((*it).first)<lookback)
{
max_val=max(max_val,
(*it).second);
++it;
}
else
{
it=m.erase(it);
}
}
return max_val;
}
The map itself is not constant, but my understanding is that the const iterator should make this fail.

The behavior has changed from C++11; std::map::erase takes const_iterator as its parameter.
void erase( iterator pos ); // (until C++11)
iterator erase( const_iterator pos ); // (since C++11)
iterator erase( iterator pos ); // (since C++17)
For std::map::erase, the passed iterator is just used as the position where the element would be deleted, not for modifying the element through it. That means const_iterator would be fine. Before C++11, the support for const_iterator was not very good, but the situation has changed from C++11. You should use const_iterator instead of iterator when possible now.

Positions are independent of the constness of their access. It was (is?) quite common that functions doing a search return const_iterator because they actually do not change the container at all. However, it is desirable to use the obtained position for mutations of the sequences, e.g., to insert() an element at the corresponding position or to erase() the located element. As a result, the container where extended to support use of const_iterator with mutating operations.
It seems the relevant paper is N2350. I'm not sure if this paper is the latest version.

Related

(C++) A question about "insert" function in vector

https://en.cppreference.com/w/cpp/container/vector/insert
Cppreference shows: iterator insert( const_iterator pos, const T& value ); and four other overloads.
But why the parameter is const_iterator but not iterator?
Whether or not the iterator is const doesn't matter, since the container is the thing being modified (and insert is not a const-qualified member function), not the passed in iterator.
And this just makes it easier to use. A non-const iterator is convertible to a const_iterator (but not the other way around) so you can still easily use an iterator.
A somewhat relevant paper: https://wg21.link/N2350

Why `std::unordered_map::erase(key_type const&)` return number of removed elements?

Recently I went to use std::unordered_map::erase, and see the function has some overloads:
Link to cppreferences
iterator erase( const_iterator pos ); (1) (since C++11)
iterator erase( iterator pos ); (1) (since C++17)
iterator erase( const_iterator first, const_iterator last ); (2) (since C++11)
size_type erase( const key_type& key ); (3) (since C++11)
And in the description of the functions return value mentioned:
Return value
1-2) Iterator following the last removed element.
3) Number of elements removed.
The last one (3), saying we could have the number of removed elements. Does that mean it's possible to have the same keys on the map?
Because there is also unordered_multimap where several elements can have the same key. Returning the number of erased elements makes perfect sense for multimap, multiset, unordered_multimap and unordered_multiset. The returning of the number of elements rather than just bool for the non-multi containers is a way to make the common "interface" of map and multimap (etc) as close to each other as possible so that in some situations either can be used via a template parameter.
Erase for a non-multi map or set (or unordered_map or unordered_set) will indeed only ever return 0 or 1.

New std::map::erase() signature C++17

According to this answer, an iterator must be implicitly convertible to const_iterator. Since that is true, as we can see happening in insert_or_assign(), then why in C++17 was a new signature added to std::map::erase()?
In C++11, we have iterator erase( const_iterator pos );
In C++17, we now have iterator erase( iterator pos );
Wasn't the C++11 signature good enough to receive iterator and const_iterator?
There's a potential ambiguity with erase(const key_type& key) when you pass an iterator. Consider the case where the key_type is something like std::any.

What to return for 'begin' and 'end' functions for my vector wrapper, for iterators?

I have a class called PointList, which holds a vector of Point * objects as its main data. I want to iterate over the points the same way you would a vector, kind of like this:
for (vector<Point *>::iterator it = point_list->begin(); it != point_list->end(); ++it)
Clearly the begin() and end() functions I write can just return the vector's begin/end functions that they hold, but what is the return type of these functions?
If I have understood the question right, the answer is right in your question. You already use the return value and type of begin and end in your piece of code.
vector<Point *>::iterator it = point_list->begin();
clearly, it holds the return value of begin() and its type is well known:
vector<Point *>::iterator
By the way, a little off-topic - why point_list is pointer to vector, not an object? And second, why it's called list, as it's vector? Use vector, or array, or sequence, but not list, as it could be misleading. list is a STL container, different from vector.
Their return type would be vector<Point *>::iterator.
You should copy the container interface, by providing two iterator types, three begin functions and three end functions. The most obvious iterator types to use are taken straight from the vector:
struct PointList {
typedef std::vector<Point*>::iterator iterator;
typedef std::vector<Point*>::const_iterator const_iterator;
iterator begin();
const_iterator begin() const;
const_iterator cbegin() const;
iterator end();
const_iterator end() const;
const_iterator cend() const;
};
cbegin() and cend() are new to C++11, they aren't in C++03. The idea is that since they don't have a non-const overload, the user can call them on a non-const container in preference to messing about with a conversion.
Since the underlying storage is in a vector, you might also consider providing (c)rbegin() and (c)rend(). In fact to implement the standard container interface you'd have to, since your iterator type is random-access. If you don't want to do that (perhaps because some future implementation of this class will not necessarily use a vector, but some other container underneath), then there is an argument for wrapping the vector's iterator in a class of your own just as you've wrapped the vector in a class of your own. That's extra work that's only needed if you need to prevent users from relying on properties of the iterator that could disappear in future implementations. You might not care about this in an internal API, more so in a published one.
They are of type vector<Point *>::iterator just like your it object. But why do you want to iterate your data outside of the container object? That would be violation of encapsulation no?

What is the difference between const_iterator and non-const iterator in the C++ STL?

What is the difference between a const_iterator and an iterator and where would you use one over the other?
const_iterators don't allow you to change the values that they point to, regular iterators do.
As with all things in C++, always prefer const, unless there's a good reason to use regular iterators (i.e. you want to use the fact that they're not const to change the pointed-to value).
They should pretty much be self-explanatory. If iterator points to an element of type T, then const_iterator points to an element of type 'const T'.
It's basically equivalent to the pointer types:
T* // A non-const iterator to a non-const element. Corresponds to std::vector<T>::iterator
T* const // A const iterator to a non-const element. Corresponds to const std::vector<T>::iterator
const T* // A non-const iterator to a const element. Corresponds to std::vector<T>::const_iterator
A const iterator always points to the same element, so the iterator itself is const. But the element it points to does not have to be const, so the element it points to can be changed.
A const_iterator is an iterator that points to a const element, so while the iterator itself can be updated (incremented or decremented, for example), the element it points to can not be changed.
Unfortunaty, a lot of the methods for the STL containers takes iterators instead of const_iterators as parameters. So if you have a const_iterator, you can't say "insert an element before the element that this iterator points to" (saying such a thing is not conceptually a const violation, in my opinion). If you want do that anyway, you have to convert it to a non-const iterator using std::advance() or boost::next(). Eg. boost::next(container.begin(), std::distance(container.begin(), the_const_iterator_we_want_to_unconst)). If container is a std::list, then the running time for that call will be O(n).
So the universal rule to add const wherever it is "logical" to do so, is less universal when it comes to STL containers.
However, boost containers take const_iterators (eg. boost::unordered_map::erase()). So when you use boost containers you can be "const agressive". By the way, do anyone know if or when the STL containers will be fixed?
Minimal runnable examples
Non-const iterators allow you to modify what they point to:
std::vector<int> v{0};
std::vector<int>::iterator it = v.begin();
*it = 1;
assert(v[0] == 1);
Const iterators don't:
const std::vector<int> v{0};
std::vector<int>::const_iterator cit = v.begin();
// Compile time error: cannot modify container with const_iterator.
//*cit = 1;
As shown above, v.begin() is const overloaded, and returns either iterator or const_iterator depending on the const-ness of the container variable:
How does begin() know which return type to return (const or non-const)?
how does overloading of const and non-const functions work?
A common case where const_iterator pops up is when this is used inside a const method:
class C {
public:
std::vector<int> v;
void f() const {
std::vector<int>::const_iterator it = this->v.begin();
}
void g(std::vector<int>::const_iterator& it) {}
};
const makes this const, which makes this->v const.
You can usually forget about it with auto, but if you starting passing those iterators around, you will need to think about them for the method signatures.
Much like const and non-const, you can convert easily from non-const to const, but not the other way around:
std::vector<int> v{0};
std::vector<int>::iterator it = v.begin();
// non-const to const.
std::vector<int>::const_iterator cit = it;
// Compile time error: cannot modify container with const_iterator.
//*cit = 1;
// Compile time error: no conversion from const to no-const.
//it = ci1;
Which one to use: analogous to const int vs int: prefer const iterators whenever you can use them (when you don't need to modify the container with them), to better document your intention of reading without modifying.
Use const_iterator whenever you can, use iterator when you have no other choice.
(as others have said) const_iterator doesn't allow you modify the elements to which it points, this is useful inside of const class methods. It also allows you to express your intent.
ok Let me explain it with very simple example first without using constant iterator
consider we have collection of random integers collection "randomData"
for(vector<int>::iterator i = randomData.begin() ; i != randomData.end() ; ++i)*i = 0;
for(vector<int>::const_iterator i = randomData.begin() ; i!= randomData.end() ; ++i)cout << *i;
As can be seen for writing/editing data inside collection normal iterator is used but for reading purpose constant iterator has been used . If you try using constant iterator in first for loop you will get error . As a thumb rule use constant iterator to read data inside collection .