43 Intermediate The Hundred
The Insert That Did Nothing
no error, no warning, and no change
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> price{{"widget", 10}, {"gadget", 25}};
const std::map<std::string, int> sale{{"widget", 7}, {"doodad", 3}};
for (const auto& item : sale)
price.insert(item); // apply the sale prices
for (const auto& [name, cost] : price)
std::cout << name << ": " << cost << '\n';
}
Run it. What is the widget on sale for?
Answer
The brand-new doodad went in at 3. The widget is still full price.
Why¶
map::insert means insert if absent. "widget" was already a key, so the container
kept the existing element and dropped your 7 on the floor — no exception, no warning,
nothing at the call site. "doodad" was new, so that one landed, which is what makes
the bug so convincing: half the loop works. insert does report what happened — it
returns a std::pair<iterator, bool> whose .second is false when the key already
existed (checking it here prints inserted=0, with the iterator pointing at the old
10) — but that return value is not [[nodiscard]], so ignoring it is perfectly quiet;
g++ 11 compiles this clean under -Wall -Wextra. emplace and insert with a hint
decline in exactly the same way, and the mirror-image trap is operator[], which
inserts when you only meant to read (entry 24).
The fix¶
Look at the bool, or call something that says "overwrite":
auto [pos, inserted] = price.insert(item); // and actually branch on `inserted`
price[name] = cost; // assignment always overwrites
price.insert_or_assign(name, cost); // C++17, and it says so in the name
The last two leave widget: 7; the first at least tells you when nothing changed.
Takeaway: insert never overwrites — it declines, and it says so only through a
bool you have to bother reading.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo