1

One advantage of try_emplace member function of std::map (and std::unordered_map) is that it does not allocate a new node if the key already exists in the map. I wonder why this member function has not been added to the std::set (and std::unordered_set) interface, where the same advantage might apply as well.

This live demo shows that std::set::emplace allocates each time: https://godbolt.org/z/MjMjPcc89 (with libstdc++).

And this benchmark shows that find + emplace may be faster than emplace alone when there are duplicated keys: https://quick-bench.com/q/2IWzv_SJFJpklGjwIKk6wgKsuz0.

However, find + emplace requires a double lookup in case the key is not present in the container. Here is the benchmark for std::map, where try_emplace is the fastest option: https://quick-bench.com/q/ymn1qaxAtrf6FTzHC98e_wkHVZ4.

EDIT

It seems the problem with allocations does not occur when std::set::insert is used. My bad I haven't tried it before.

Live demo: https://godbolt.org/z/EjWjfjnsc

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
  • 1
    you need a `Key` for `try_emplace`. Sets don't have keys, but you need to construct the whole element to be able to compare it to others. – 463035818_is_not_an_ai Apr 12 '22 at 10:46
  • @463035818_is_not_a_number Sets don't have keys? Quoting from the standard: _"A set is an associative container that supports unique keys (contains at most one of each key value) and provides for fast retrieval of the keys themselves."_ (http://eel.is/c++draft/set#overview-1.sentence-1) – Daniel Langr Apr 12 '22 at 10:48
  • well ok they are called Keys, but thats already the element. In maps you have Keys and the mapped value, and you only need the Key to see if inserting needs to take place. Anyhow, I am not sure if this is a good argument, in particular I am not sure if it is possible to use an overloaded comparator that would allow to compare some `T` to the keys – 463035818_is_not_an_ai Apr 12 '22 at 10:50
  • The difference is, that if you add a value to a set, which already exists in the set, the set remains unchanged (no duplicates etc.). So, the adding to a set always succeeds, independent of whether the item is in the set already or not. – BitTickler Apr 12 '22 at 10:50
  • 1
    @BitTickler why is that a difference? If you try to insert into a map where the Key is already present then the map is not modified as well – 463035818_is_not_an_ai Apr 12 '22 at 10:54
  • @463035818_is_not_a_number `try_emplace` for `set` could require a key as an argument. The same holds for `std::map::try_emplace`. – Daniel Langr Apr 12 '22 at 10:57
  • @DanielLangr - So you want to save on constructing the value type (which is the key), by adding the constructed key? – StoryTeller - Unslander Monica Apr 12 '22 at 10:58
  • @StoryTeller-UnslanderMonica I want to save on allocations. – Daniel Langr Apr 12 '22 at 10:59
  • 3
    If it "could require a key as an argument", then, @DanielLangr, how would that differ from `insert()`? – Sam Varshavchik Apr 12 '22 at 10:59
  • @SamVarshavchik It won't. But insert always allocates a node. – Daniel Langr Apr 12 '22 at 11:00
  • 1
    @DanielLangr - That's what plain emplace does already. Construct the key, but don't allocate internal data until insertion is needed. – StoryTeller - Unslander Monica Apr 12 '22 at 11:00
  • This sounds like a defective implementation of `insert()`. Nothing in the standard requires that. – Sam Varshavchik Apr 12 '22 at 11:01
  • @StoryTeller-UnslanderMonica Please, see the godbolt link. There, inserting allocates 10 times when the same key is 10 times used. But I just checked it does not happen with `insert`, only with `emplace`: https://godbolt.org/z/EjWjfjnsc. It seems you're right. – Daniel Langr Apr 12 '22 at 11:02
  • @DanielLangr - I've seen the link, a sub-optimal implementation doesn't negate the leeway given by the specification. – StoryTeller - Unslander Monica Apr 12 '22 at 11:04
  • @SamVarshavchik It seems that with `insert`, the allocation does not happen with the same key. My bad I haven't tried it before. – Daniel Langr Apr 12 '22 at 11:04
  • You could use `lower_bound` (maybe `upper_bound`, some experimentation is needed to see which one works better) followed by `emplace_hint`, if needed, to avoid most of the cost of a 2nd lookup, in order to work around this implementation. This is most likely a gamble that most use cases of std::set do not attempt to insert a large number of duplicate keys, so its optimized for the most common use case. – Sam Varshavchik Apr 12 '22 at 11:07
  • 1
    i remember to have read some guide that suggested to use `emplace` rather than `insert` because it can do the same. This seems to be a counter example. Though I suppose nothing in the standard would forbid to have a specialization for emplace that does not allocate when not required – 463035818_is_not_an_ai Apr 12 '22 at 11:10
  • @463035818_is_not_a_number In case of sets, you cannot change the value (the key is the value), in contrast to a map, where you can overwrite the value under the same key: `std::map m; m[0] = 0; m[0] = 1;` This is a non-issue with sets. – BitTickler Apr 12 '22 at 11:15
  • @BitTickler but both `insert` and `emplace` are about inserting a new element, not about modifying an existing one. – 463035818_is_not_an_ai Apr 12 '22 at 11:16
  • Insert is actually an "update" if the key already exists in a map. And it potentially changes the maps content, whereas with sets, this is not the case. – BitTickler Apr 12 '22 at 11:17
  • @463035818_is_not_a_number Yes, this is a counter-example. I maybe won't delete the question for this reason. Anyway, `emplace` needs to construct the key and if it won't happen in the allocated node directly, it would also then need to copy or move the key in case of insertion. Which I don't know if it's allowed. But yes, there may be specializations allowed for `emplace` if the key type is the type of the argument. That's a good question. – Daniel Langr Apr 12 '22 at 11:20
  • @BitTickler Insert with `insert` or `emplace` does not change the map if the key already exists in it. `operator[]` is a different case, which it's off-topic to this question. – Daniel Langr Apr 12 '22 at 11:28
  • @BitTickler thats not correct. `insert` does not modify an element when an element with equivalent key is already present https://godbolt.org/z/Er648xbTn – 463035818_is_not_an_ai Apr 12 '22 at 11:28
  • You are right! C++17 mixed it up some. The function I had in mind goes by `insert_or_assign()`. They could as well have just called it "upsert()" as that name is well known e.g. in the database world. My prime c++ times were before C++11 and so I do not keep up well with that moving target ;) – BitTickler Apr 12 '22 at 11:45

0 Answers0