Skip to content

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:

error: assignment of read-only location
       ‘it.std::_Rb_tree_const_iterator<int>::operator*()’

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:

ids.erase(it);
ids.insert(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.

Open in Compiler Explorer ↗ Open SHOW_BUG variant ↗ Quiz this entry