bug in VS2019 C++14 code optimization, or my fault?

I know that compiler is usually the last thing to blame for bugs in a code, but I do not see any other explanation for the following behaviour of the following C++ code (distilled down from an actual project):

#include <iostream>
#include <map>

int main()
{
    auto values = { 1, 3, 5 };
    std::map<int, int> valMap;

    for (auto const & val : values) {
        std::cout << "before assignment: valMap.size() = " << valMap.size();
        valMap[val] = valMap.size();
        std::cout << " -> set valMap[" << val << "] to " << valMap[val] << "\n";
    }
}

The expected output of this code is:

before assignment: valMap.size() = 0 -> set valMap[1] to 0
before assignment: valMap.size() = 1 -> set valMap[3] to 1
before assignment: valMap.size() = 2 -> set valMap[5] to 2

However, when I build a Release version with the (default) C++14 compiler, the output becomes:

before assignment: valMap.size() = 0 -> set valMap[1] to 1
before assignment: valMap.size() = 1 -> set valMap[3] to 2
before assignment: valMap.size() = 2 -> set valMap[5] to 3

In other words, all values in valMap are larger by 1 than what they should be – it looks like the map gets appended before the right-hand-side of the assignment is evaluated.

This happens only in a Release build with C++14 language standard (which is the default in VS2019). Debug builds work fine (I hate when this happens – it took me hours to find out what is going on), as do Release builds of C++17 and C++20. This is why it looks like a bug to me.

My question is: is this a compiler bug, or am I doing something wrong/dangerous by using .size() in the assignment?

>Solution :

The evaluation order of A = B was not specified before c++17, after c++17 B is guaranteed to be evaluated before A, see https://en.cppreference.com/w/cpp/language/eval_order rule 20.

The behaviour of valMap[val] = valMap.size(); is therefore unspecified in c++14, you should use:

auto size = valMap.size();
valMap[val] = size;

Or avoid the problem by using emplace which is more explicit than relying on [] to automatically insert a value if it doesn’t already exist:

valMap.emplace(val, size);

Leave a Reply