c++hashhashmap

Need help defining hash function for custom class in C++


i have a custom Class Identifier shown below and require help in understanding the compile-time error that i am seeing due to defining a hash function for the custom user class Identifier. The class looks like below

struct Identifier {
    
    
Identifier(const std::string &domain, 
           const std::string &name,
           const std::string &version)
: domain(domain), name(name), version(version) {}
    
/// The domain associated with the identifier.
  std::string domain;
    
/// The name associated with the identifier.
   std::string name;

  /// The version associated with the identifier.
  std::string version;
};


inline bool operator==(const Identifier& lhs, const Identifier& rhs){
    return ((lhs.domain == rhs.domain) && (lhs.name == rhs.name) && (lhs.version == rhs.version));
}

I have an interface ModuleInterface.h that relies on Identifier and is as follows

class ModuleInterface {
public:

  using Identifiers = std::unordered_set<Identifier>;

  virtual Identifiers getSupportedIdentifiers() = 0;
  
};

There is a Manager class that has a registerModule method that takes in ModuleInterfaces shown below

class Manager{
public:

    bool registerModule(const std::shared_ptr<ModuleInterface> &module);


private:
    std::map<Identifier, std::shared_ptr<ModuleInterface>> m_requestHandlers;
};

The implementation of the Manager.cpp class is as follows

bool Manager::registerModule(const std::shared_ptr<ModuleInterface>& module){
    /*
    for(auto& requestIdentifier : module->getSupportedIdentifiers()){
        //m_requestHandlers[requestIdentifier] = module;
    } */

    for (auto itr = module->getSupportedIdentifiers().begin(); itr != module->getSupportedIdentifiers().end(); ++itr) {
     m_requestHandlers[*itr] = module;
    }


    return true;
}

Now when i try to compile it i see compile time error and the error looks like below

foo/src/Manager.cpp:17:21: warning: object backing the pointer will be destroyed at the end of the full-expression [-Wdangling-gsl]
    for (auto itr = module->getSupportedIdentifiers().begin(); itr != module->getSupportedIdentifiers().end(); ++itr) {
                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from foo/src/Manager.cpp:2:
In file included from foo/include/Registration/Manager.h:10:
In file included from bar/usr/include/c++/v1/map:550:
In file included from bar/usr/include/c++/v1/functional:515:
In file included from bar/usr/include/c++/v1/__functional/boyer_moore_searcher.h:25:
In file included from bar/usr/include/c++/v1/unordered_map:523:
bar/usr/include/c++/v1/__hash_table:838:5: error: static assertion failed due to requirement 'integral_constant<bool, false>::value': the specified hash does not meet the Hash requirements
    static_assert(__check_hash_requirements<_Key, _Hash>::value,
    ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bar/usr/include/c++/v1/__hash_table:853:1: note: in instantiation of template class 'std::__enforce_unordered_container_requirements<Identifier, std::hash<Identifier>, std::equal_to<Identifier>>' requested here
typename __enforce_unordered_container_requirements<_Key, _Hash, _Equal>::type
^
bar/usr/include/c++/v1/unordered_set:614:30: note: while substituting explicitly-specified template arguments into function template '__diagnose_unordered_container_requirements' 
        static_assert(sizeof(__diagnose_unordered_container_requirements<_Value, _Hash, _Pred>(0)), "");
                             ^
foo/src/Manager.cpp:17:29: note: in instantiation of member function 'std::unordered_set<Identifier>::~unordered_set' requested here
    for (auto itr = module->getSupportedIdentifiers().begin(); itr != module->getSupportedIdentifiers().end(); ++itr) {
                            ^
1 warning and 1 error generated.
ninja: build stopped: subcommand f

any help or pointers understanding what i am missing is greatly would be appreciated as i am not understanding why the hash method is not meeting the requirement because it will return a bool value based on lhs and rhs.

---update posting minimal repro example---

Identifier.h

#pragma once

#include <string>

struct Identifier {


    Identifier(const std::string &domain,
               const std::string &name,
               const std::string &version)
            : domain(domain), name(name), version(version) {}

    /// The domain associated with the identifier.
    std::string domain;

    /// The name associated with the identifier.
    std::string name;

    /// The version associated with the identifier.
    std::string version;
};


inline bool operator==(const Identifier& lhs, const Identifier& rhs){
    return ((lhs.domain == rhs.domain) && (lhs.name == rhs.name) && (lhs.version == rhs.version));
}

ModuleInterface.h

#include "Identifier.h"
#include <unordered_set>

class ModuleInterface {
public:

    using Identifiers = std::unordered_set<Identifier>;

    virtual Identifiers getSupportedIdentifiers() = 0;

};

Manager.h

#pragma once

#include "Identifier.h"
#include "ModuleInterface.h"
#include <map>
namespace test {
class Manager {
public:
    Manager() = default;
    bool registerModule(const std::shared_ptr<ModuleInterface>& module);

private:
    std::map<Identifier, std::shared_ptr<ModuleInterface>> m_requestHandlers;
};

}

Manager.cpp

#include "Manager.h"

namespace test{

bool Manager::registerModule(const std::shared_ptr<ModuleInterface>& module){

    for(auto& requestIdentifier : module->getSupportedIdentifiers()){
        m_requestHandlers[requestIdentifier] = module;
    }
    /*
    for (auto itr = module->getSupportedIdentifiers().begin(); itr != module->getSupportedIdentifiers().end(); ++itr) {
        m_requestHandlers[*itr] = module;
    }*/


    return true;
}

}

Error

/Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk/usr/include/c++/v1/__hash_table:697:5: error: static assertion failed due to requirement 'integral_constant<bool, false>::value': the specified hash does not meet the Hash requirements
    static_assert(__check_hash_requirements<_Key, _Hash>::value,
    ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk/usr/include/c++/v1/__hash_table:712:1: note: in instantiation of template class 'std::__enforce_unordered_container_requirements<Identifier, std::hash<Identifier>, std::equal_to<Identifier>>' requested here
typename __enforce_unordered_container_requirements<_Key, _Hash, _Equal>::type
^
/Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk/usr/include/c++/v1/unordered_set:715:30: note: while substituting explicitly-specified template arguments into function template '__diagnose_unordered_container_requirements' 
        static_assert(sizeof(std::__diagnose_unordered_container_requirements<_Value, _Hash, _Pred>(0)), "");
                             ^
/Users/test/src/Manager.cpp:7:43: note: in instantiation of member function 'std::unordered_set<Identifier>::~unordered_set' requested here
    for(auto& requestIdentifier : module->getSupportedIdentifiers()){

Solution

  • It has nothing to do with pointers, or your Manager or ModuleInterface design. The problem boils down to:

    struct Identifier {
      // side note: better to take args by value, and then std::move
      Identifier(const std::string &d, const std::string &n,
                 const std::string &v)
          : domain(d), name(n), version(v) {}
    
      std::string domain;
      std::string name;
      std::string version;
      auto operator<=>(const Identifier&) const = default;  // to avoid boilerplate code, since C++20
    };
    
    // this is OK
    std::map<Identifier, int> myMap; // value type doesn't really matter here
    // this will not compile
    std::unordered_set<Identifier> mySet;
    

    Comparator for keys of std::map is std::less<Key>, so std::less<Identifier> in your case, which can be easily taken care of by defaulting the comparison operator.

    On the other hand, std::unordered_set needs std::equal_to<Key> (which you have), and std::hash<Key> (which you don't). One of the easy ways to make your Identifier "hashable" is to simply copy paste the example of ustom specialization of std::hash injected in namespace std from cppreference:

    template<>
    struct std::hash<Identifier>
    {
        std::size_t operator()(const Identifier& id) const noexcept
        {
            std::size_t h1 = std::hash<std::string>{}(id.domain);
            std::size_t h2 = std::hash<std::string>{}(id.name);
            std::size_t h3 = std::hash<std::string>{}(id.version);
            return h1 ^ (h2 << 1) ^ (h3 << 2);
        }
    };
    

    Note: example hash function above is only for demonstration purpose, and might not be very efficient.