c++boostunordered-mapheterogeneousboost-unordered

How can I use heterogenous key types with boost::unordered_flat_map


Boost unordered_flat_map is pretty good, it's way faster than std::unordered_map. I'm trying to use it with heterogenous key types:

boost::unordered::unordered_flat_map<std::string, int> my_map;
my_map.emplace(std::string("Hello"), 1);
my_map.find(std::string_view("Hello")); 
// no instance of overloaded function matches the argument list
            argument types are: (std::string_view)

Now, because I created a map that has key type std::string there is a find function that takes a std::string, but there's also a templated find function that takes a K, and usually these things are supposed to work provided the container:

  1. Knows how to hash the heterogenous type, and
  2. Knows of a equality operator that takes the heterogenous type as the right hand side.

Even if I do:

auto equal_to = [](auto& lhs, auto& rhs) { return lhs == rhs; };

And pass this equality class to the container I still get the error:

boost::unordered::unordered_flat_map<std::string, int, decltype(equal_to)> my_map;

How am I supposed to get this to work?


Solution

  • From cppreference we learn that transparent hash/equality enable heterogenous key lookup:

    3,4) Finds an element with key that compares equivalent to the value x. This overload participates in overload resolution only if Hash::is_transparent and KeyEqual::is_transparent are valid and each denotes a type. This assumes that such Hash is callable with both K and Key type, and that the KeyEqual is transparent, which, together, allows calling this function without constructing an instance of Key.

    So, you might do that:

    struct MyHash : std::hash<std::string_view> {
        using is_transparent = std::true_type; // actual type doesn't matter
    };
    

    Now, for the equal_to we can be lazy and use std::equal_to</*void*/> which is already transparent:

    Live On Coliru

    #include <boost/unordered/unordered_flat_map.hpp>
    using namespace std::literals;
    
    struct MyHash : std::hash<std::string_view> {
        using is_transparent = std::true_type; // actual type doesn't matter
    };
    
    int main() {
        boost::unordered::unordered_flat_map<std::string, int, MyHash, std::equal_to<>> my_map;
        auto [where,ok] = my_map.emplace("Hello", 1);
        assert(ok);
    
        auto found = my_map.find("Hello"sv);
        assert(found != my_map.end());
        assert(found == where);
    }
    

    Passing all asserts. In my code I would probably not rely on equal_to<void> to prevent surprises. You could substitute Boost's "Rosetta Stone" core::string_view¹. You could also make the hash even more tolerant (e.g. expressing it in terms of Boost ContainerHash).


    ¹ which is interoperable among many, like boost::beast::string_view and std::string_view and many others. It does, however, not support configurable char-traits.