Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++: conditionally inserting a key and value into a std::map

Tags:

c++

If I do:

std::map<string, size_t> word_count;
size_t value = word_count.count("a") == 0 ? 1 : 2;
word_count["a"] = value;

then the final value of word_count["a"] is 1, as I would expect. If instead I do:

std::map<string, size_t> word_count;
word_count["a"] = word_count.count("a") == 0 ? 1 : 2;

then the final value of word_count["a"] is 2. Why!?

like image 713
jesper Avatar asked Apr 06 '13 21:04

jesper


2 Answers

Officially, either side of the assignment could be evaluated first. It's up to the implementation to decide which. If word_count does not contain an "a", one is inserted, and an lvalue reference to it returned. If word_count does contain one, only the latter part happens. Despite the uncertainty of which side is evaluated first, you can follow possible executions:

Left Side First

No Element Exists:

operator[] inserts the element since it isn't already there. count() finds it and returns 1, so you end up with it being assigned a value of 2.

Element Exists:

operator[] returns the existing element and count() finds it and returns 1, so you end up with it being assigned a value of 2.

Right Side First

No Element Exists:

count() returns 0, so you get 1 from the right side. Then, "a" is inserted into the map and assigned a value of 1.

Element Exists:

count() returns 1, so you get 2 from the right side. Then, word_count["a"] is accessed and has 2 assigned to it.

Conclusion

In short, you can't rely on this to do what you want, so it's better to use something that you can rely on. mrfontanini made a good suggestion, so I'll edit it a bit:

word_count["a"]++;
word_count["a"] = std::min(word_count["a"], 2);

The first line ensures it is inserted and has a value of at least 1. The second limits that value to a maximum 2, in the case that you do this operation repeatedly.

Notes

I base this answer off of two things:

  1. When a side is picked to be evaluated, the whole side has to be evaluated before the other side can start.

  2. Constructs such as word_count["a"] = 1 exhibit well-defined behaviour, even in the case that an element is inserted and then assigned to.

There is some debate and discussion below about whether these are true or not. I've made it a bit more official now.

like image 51
chris Avatar answered Sep 21 '22 02:09

chris


Looking at this line:

word_count["a"] = word_count.count("a") == 0 ? 1 : 2;

I believe that if word_count["a"] does not exist in the map before that line is executed, you cause undefined behavior.

This is because word_count["a"] will create an entry in the map if one doesn't exist, and this will change the behavior of word_count.count("a"). We also have no required sequencing between those two calls..

like image 32
Bill Lynch Avatar answered Sep 20 '22 02:09

Bill Lynch