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 astd::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));
}