Sobes.tech
Middle

What needs to be done to store your structure in an unordered_map?

sobes.tech AI

Answer from AI

To store a structure as a key in std::unordered_map, you need to provide:

  1. A hash function: Finds a hash value for an instance of the structure.
  2. An equality comparison operator (operator==): Compares two instances of the structure for equality.

There are several ways to provide these elements:

  • Overload operator== inside the structure and specialize std::hash for your structure. This is the most common and recommended approach.

    #include <unordered_map>
    #include <string>
    #include <functional>
    
    struct MyStruct {
        int id;
        std::string name;
    
        bool operator==(const MyStruct& other) const {
            return id == other.id && name == other.name;
        }
    };
    
    // Specialization of std::hash for MyStruct
    namespace std {
        template <>
        struct hash<MyStruct> {
            size_t operator()(const MyStruct& obj) const {
                size_t h1 = hash<int>()(obj.id);
                size_t h2 = hash<std::string>()(obj.name);
                // Combine hashes
                return h1 ^ (h2 << 1);
            }
        };
    }
    
    // Usage example in main
    int main() {
        std::unordered_map<MyStruct, int> my_map;
        MyStruct key1 = {1, "Alice"};
        MyStruct key2 = {2, "Bob"};
        MyStruct key3 = {1, "Alice"}; // Equivalent to key1
    
        my_map[key1] = 10;
        my_map[key2] = 20;
    
        // Access with an equivalent key
        if (my_map.count(key3)) {
            // my_map[key3] will give 10
        }
    
        return 0;
    }
    
  • Provide hash and comparison functions as separate template parameters when declaring unordered_map. This is less convenient if you frequently use the structure as a key, as you need to specify the comparator and hasher types each time.

    #include <unordered_map>
    #include <string>
    #include <functional>
    
    struct MyStruct {
        int id;
        std::string name;
    };
    
    // Separate hash function
    struct MyStructHash {
        size_t operator()(const MyStruct& obj) const {
            size_t h1 = std::hash<int>()(obj.id);
            size_t h2 = std::hash<std::string>()(obj.name);
            return h1 ^ (h2 << 1);
        }
    };
    
    // Separate comparison function (or functor)
    struct MyStructEqual {
        bool operator()(const MyStruct& lhs, const MyStruct& rhs) const {
            return lhs.id == rhs.id && lhs.name == rhs.name;
        }
    };
    
    // Usage example in main
    int main() {
        std::unordered_map<MyStruct, int, MyStructHash, MyStructEqual> my_map;
        MyStruct key1 = {1, "Alice"};
        MyStruct key2 = {2, "Bob"};
    
        my_map[key1] = 10;
        my_map[key2] = 20;
    
        return 0;
    }
    

Important points:

  • Correctness of the hash function: For any two keys a and b, if a == b, then hash(a) must be equal to hash(b). The reverse is not required (hash collisions are allowed).
  • Hash function quality: A good hash function distributes hash values evenly for different keys, minimizing collisions and improving performance (O(1) on average).
  • Const correctness: The comparison operators and hash function should be const as they should not modify the key object.