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

C++C++14

C++ Problem Overview


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?

C++ Solutions


Solution 1 - C++

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).

Solution 2 - C++

> 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.

Solution 3 - C++

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjmaView Question on Stackoverflow
Solution 1 - C++Some programmer dudeView Answer on Stackoverflow
Solution 2 - C++Ted LyngmoView Answer on Stackoverflow
Solution 3 - C++paddyView Answer on Stackoverflow