I have code like this:
std::map<int, const Data> all_data;
// ...
bool OldDataIsBetter(Data const& oldData, Data const& newData) {...}
// ...
void AddData(int key, Data&& newData)
{
auto [it, ok] = all_data.try_emplace(key, std::move(newData));
if (!ok)
{
if (OldDataIsBetter(*it, newData)) return;
it->second = std::move(newData);
}
}
This doesn't compile, because it->second
refers to a const Data
and so its assignment operator cannot be called. It works fine if const
is removed.
The intent of the above is to insert_or_assign
, except that if an item is already present then I need to compare the old and new items to see which one is "better".
The intent of declaring the map element type with const
is that the data is supposed to be immutable once in the map -- the item as a whole can be replaced but not piecemeal modified.
I can "fix" the above by reassigning to the container instead:
all_data[key] = std::move(newData);
(Actually turns out that has the same problem with const
.)
Or by erasing and retrying the emplace:
all_data.erase(it);
all_data.emplace(key, std::move(newData)); // should never fail
But neither of these seem elegant, since I already have an iterator pointing to the item that should be replaced, and both of the above forget that and go search again.
Is there a better way to accomplish this replacement?
TLDR from a chat thread brought up related questions:
- If
extract
can remove a node from a container, let you modify its otherwise-const key, and reinsert it -- all without any reallocations -- why is this not possible for a const mapped_value? - Const objects can be destroyed. This should also apply to const objects inside containers -- and indeed that's what the erase/emplace variant might be doing if it happens to reuse the same storage for the node (either by coincidence or through a custom allocator). Why then isn't there a way to
replace
a const mapped_value with a different const mapped_value without reallocating the map node that contains it?