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()){
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.