Skip to content

Intermediate Gotchas

The Value Built for a Failed Insert

the map keeps its old value after loading a new one

std::string load_value() {
  std::cout << "loading value\n";
  return "new";
}

int main() {
  std::map<int, std::string> records{{1, "old"}};
  records.emplace(1, load_value());

  std::cout << "stored: " << records.at(1) << '\n';
}

Does load_value run when key 1 already exists?

Answer
loading value
stored: old

The map declined the insert, but it could not uncall the value factory.

Why

C++ evaluates load_value() before entering map::emplace, so collision detection cannot save that work. emplace then finds key 1 and keeps the old mapped value. try_emplace(1, load_value()) has the same eager call: it can suppress construction in the container, but it cannot undo a function expression that already ran. Establish that the key is absent before invoking an expensive factory.

The fix

if (records.find(1) == records.end())
  records.emplace(1, load_value());

Takeaway: emplace may skip insertion, never evaluation of the arguments you passed to it.

Try it: g++ -std=c++17 main.cpp -o demo && ./demo

Open in Compiler Explorer ↗ Quiz this entry