Assignment in C++ occurs despite exception on the right side

I have some (C++14) code that looks like this:

map<int, set<string>> junk;
for (int id : GenerateIds()) {
    try {
        set<string> stuff = GetStuff();
        junk[id] = stuff;
    } catch (const StuffException& e) {
        ...
    }
}

This works. Sometimes GetStuff() throws an exception, which works fine, because if it does, I don't want a value in the junk map then.

But at first I'd written this in the loop, which doesn't work:

junk[id] = GetStuff();

More precisely, even when GetStuff() throws an exception, junk[id] is created (and assigned an empty set).

This isn't what I'd expect: I'd expect them to function the same way.

Is there a principle of C++ that I've misunderstood here?


Before C++17 there was no sequencing between the left- and right-hand side of assignment operators.

It's first in C++17 that explicit sequencing was introduced (right-hand side is evaluated first).

That means the evaluation order is unspecified, which means it's up to the implementation to perform the evaluation in the order in which it wants, and in this case it evaluates the left-hand side first.

See this evaluation order reference for more details (especially point 20).


std::map::operator[]

Returns a reference to the value that is mapped to a key equivalent to key, performing an insertion if such key does not already exist.

junk[id] causes the above mentioned insertion and after that has already happened GetStuff() throws. Note that in C++14 the order in which these things happen is implementation defined so with a different compiler your junk[id] = GetStuff(); may not do the insertion if GetStuff() throws.


You're misunderstanding how operator[] works on std::map.

It returns a reference to the mapped item. Therefore, your code is first inserting a default item in that position and then invoking operator= to set a new value.

To make this work the way you expect, you'll need to use std::map::insert (*):

junk.insert(std::make_pair(id, GetStuff()));

Caveat: insert will only add the value if id is not already mapped.