c++unordered-mapstd-rangesrange-based-loop

Deleting map elements in a range-based loop


I would like to drop a number of elements from a map based on some condition:

#include <unordered_map>
#include <ranges>
#include <iostream>

int main() {

    std::unordered_map<int, int> numbers = {{1,2}, {2,1}, {3,2}, {4,5}};

    auto even = [](auto entry){return entry.second %2 == 0;};
    for(auto& [key, val] : numbers | std::views::filter(even)) {
        numbers.erase(val);
    }

    for(auto& [key, val] : numbers) {
        std::cout << key << " " << val << "\n";
    }
}

But it seems there I am invalidating iterators that the range-based loop needs:

4 5
3 2
1 2

I know how to do this explicitly using iterators, but is there a nice and concise range-based way to delete elements based on a filter?


Solution

  • I suggest you use std::erase_if(), like below:

    std::erase_if(numbers, [](auto entry) {return entry.second % 2 == 0; });
    

    If you want a ranges solution, and you do not need in-place changing, you can do something like below (this code compiles only in C++23):

    numbers = numbers | std::views::filter(even) | std::ranges::to<decltype(numbers)>();
    

    I am not sure, but maybe the below code is better performance than normal ranges code as above, based on documentation:

    auto temp_numbers = numbers | std::views::filter(even) | std::ranges::to<decltype(numbers)>();
    numbers.swap(temp_numbers);
    

    As you can see in the operator= for std::unordered_map, the complexity of its move assignment operator is linear, but the complexity of its move constructor is constant, and the complexity of its swap() method is constant, so it seems to be better performance. But, I do not have any benchmark for this.