C++ std::set update is tedious: I can't change an element in place

I find the update operation on std::set tedious since there's no such an API on cppreference. So what I currently do is something like this:

//find element in set by iterator
Element copy = *iterator;
... // update member value on copy, varies
Set.erase(iterator);
Set.insert(copy);

Basically the iterator return by Set is a const_iterator and you can't change its value directly.

Is there a better way to do this? Or maybe I should override std::set by creating my own (which I don't know exactly how it works..)


Solution 1:

set returns const_iterators (the standard says set<T>::iterator is const, and that set<T>::const_iterator and set<T>::iterator may in fact be the same type - see 23.2.4/6 in n3000.pdf) because it is an ordered container. If it returned a regular iterator, you'd be allowed to change the items value out from under the container, potentially altering the ordering.

Your solution is the idiomatic way to alter items in a set.

Solution 2:

There are 2 ways to do this, in the easy case:

  • You can use mutable on the variable that are not part of the key
  • You can split your class in a Key Value pair (and use a std::map)

Now, the question is for the tricky case: what happens when the update actually modifies the key part of the object ? Your approach works, though I admit it's tedious.

Solution 3:

In C++17 you can do better with extract(), thanks to P0083:

// remove element from the set, but without needing
// to copy it or deallocate it
auto node = Set.extract(iterator);
// make changes to the value in place
node.value() = 42;
// reinsert it into the set, but again without needing 
// to copy or allocate
Set.insert(std::move(node));

This will avoid an extra copy of your type and an extra allocation/deallocation, and will also work with move-only types.

You can also extract by key. If the key is absent, this will return an empty node:

auto node = Set.extract(key);
if (node) // alternatively, !node.empty()
{
    node.value() = 42;
    Set.insert(std::move(node));
}