44 Intermediate The Hundred
The Element You Cannot Touch
a mutable set, an iterator in hand, and still no way in
#include <iostream>
#include <set>
int main() {
std::set<int> ids{10, 20, 30};
auto it = ids.find(20);
*it = 99; // a non-const set, a non-const iterator... right?
for (int id : ids)
std::cout << id << ' ';
std::cout << '\n';
}
Compile it. What happens?
Answer
It doesn't compile. ids is mutable, but its elements are not — set<int>::iterator is
a constant iterator, so *it is a const int&.
Why¶
A std::set is a sorted tree, and an element's value is its place in that tree.
Assign through an iterator and the element keeps its old place while claiming a new value —
the tree is now unsorted, and every later find and insert quietly gives wrong answers.
So the library removes the option: in a set, both iterator and const_iterator are
constant iterators. Whether they are even the same type is unspecified, but every mainstream
implementation makes them one — hence the const_iterator in GCC 11's rejection:
The same rule applies to the key half of a std::map element, whose value_type is
std::pair<const Key, T> — it->first = x is an error, it->second = x is fine.
The fix¶
Take the element out and put a new one back — the default build does exactly that and
prints 10 30 99:
Since C++17 a node handle moves
the tree node itself instead of allocating a fresh one:
auto n = ids.extract(20); n.value() = 99; ids.insert(std::move(n));
Better still, keep mutable data out of the sorted position: a std::map<Id, Account>
freezes the key and leaves the value yours to edit.
Takeaway: anything the container sorts by is const to you — changing it means
removing and re-inserting.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo — add -DSHOW_BUG to meet the error.