Iterator invalidation for associative containers

67 Views Asked by At

I know that erasing elements from an associative container inside a for loop invalidates it.

Is it the case when using a range based loop?

#include <iostream>
#include <unordered_map>
#include <set>
#include <algorithm>
#include <ranges>

struct A {
    int value;
    // other members...
};

int main() {
    std::unordered_map<std::string, std::set<A>> myMap;

    // Assuming you have populated the map and the set

    // Object of type A
    A a;

    for (auto& [key, valueSet] : myMap) {
        valueSet.erase(a);

        if (valueSet.empty()) {
            myMap.erase(key);
        }
    }

    return 0;
}

And how can the compiler detects it.

I run -fsanitize=address,undefined but gcc doesn't show any warning.

1

There are 1 best solutions below

0
Jan Schultke On BEST ANSWER

Is it the case when using a range based loop?

Generally yes, a range-based for loop is just using iterators under the hood. It effectively expands to:

{   // for (init-statement; range-declaration : range-expression) { ... }
    /* init-statement */
    auto && __range = /* range-expression */;
    auto __begin = /* begin-expr */;
    auto __end = /* end-expr */;
    for ( ; __begin != __end; ++__begin) {
        /* range-declaration */ = *__begin;
        /* ... */
    }
}

Any operation which invalidates iterators might also interfere with a range-based for loop as a result.

std::unordered_map::erase invalidates the iterator to the erased element. In your case, erase(key) is unsafe, because it invalidates the __begin iterator which is used under the hood. ++__begin would then advance an invalidated iterator, which is undefined behavior.

-fsanitize=address,undefined does catch this type of mistake. (See live example at Compiler Explorer). It is a heap-use-after-free. Are you sure that -fsanitize is even supported on your platform? Maybe it doesn't do anything because you're using MinGW and an old version of GCC.