I am trying to implement a class in C++, and I want each class to have its own implementation of hashcode(basically to use it as a key in unordered_map
& unordered_set
)
for eg:
class CustomClass{
int a;
vector<int> b;
string c;
bool operator ==(const CustomClass& o) const{
return ((a == o.a) && (b == o.b) && (c == o.c));
}
/*
Is it possible to define the hashcode function here instead of defining it outside the class.
size_t operator()() const {
// Some custom logic for calculating hash of CustomClass using
// the hash Values of its individual fields
std::size_t h = 0;
for(int& t : b){
h = (h ^ std::hash<int>()(t)) << 1;
}
return (h^(std::hash<int>()(a) << 1))^( std::hash<string>()(c) << 1);
}
*/
};
Now, suppose I want to use this in an unordered_map like
int main(){
unoredered_map<CustomClass, int> m;
}
I have two options,
i) Inject the hashcode in std namespace with Template specialization
namespace std {
template <> struct hash<CustomClass> {
size_t operator()(const CustomClass& o) const {
// Some custom logic for calculating hash of CustomClass using
// the hash Values of its individual fields
size_t h = 0;
for(int& t : o.b){
h = (h ^ std::hash<int>()(t)) << 1;
}
return (h^(std::hash<int>()(o.a) << 1))^( std::hash<string>()(o.c) << 1);
}
};
}
OR
ii.) Specify this function while creating the unordered_map
(or unordered_set
) every time while instantiating it, i.e
struct HashSpecialer {
std::size_t operator()(const CustomClass& o) const {
std::size_t h = 0;
for(int& t : o.b){
h = (h ^ std::hash<int>()(t)) << 1;
}
return (h^(std::hash<int>()(o.a) << 1))^( std::hash<string>()(o.c) << 1);
}
};
and while instantiating the unordered_map
, I have provide this struct.
int main(){
unoredered_map<CustomClass, int, HashSpecialer> m;
}
I find both the methods, confusing to use (i) Pollutes the std namespace and (ii) makes it hard by remembering to provide the HashSpecializer every time I instantiate an unordered_map
Is it possible to provide the hashcode function within the class definition itself , as I described in the commented section in the code snippet above
Note : In java , we can override the hashCode() method in the class and we could achieve this functionality. Once I override the hashCode() method, I need not worry about it later.
public class CustomClass {
int a;
List<Integer> b;
String c;
// I Let my IDE generate these methods :D
@Override public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
CustomClass that = (CustomClass) o;
if (a != that.a)
return false;
if (b != null ? !b.equals(that.b) : that.b != null)
return false;
return c != null ? c.equals(that.c) : that.c == null;
}
// This one too :D
@Override public int hashCode()
{
int result = a;
result = 31 * result + (b != null ? b.hashCode() : 0);
result = 31 * result + (c != null ? c.hashCode() : 0);
return result;
}
}
I am looking for something like this as this proves to be very handy.
That's what namespaces are for. Your problem solves itself :)
C++ is not Java. Forget any Java you know, it'll help you tremendously. Lots of Java idioms look very different in C++, or are simply useless or inapplicable in C++.
I think the solution to your problem is to adjust your understanding of what is an aesthetically pleasing C++ program.
A specialization of
std::hash
doesn't pollute the std namespace, instead you should consider thatstd::hash
is the customization point for controlling howunordered_map
works with your class.Such a specialization is part of the interface of the class (and can be a friend of the class) in exactly the same way as binary operators like
operator +()
should be a non-member functions, and still be part of the interface.C++'s design might be more verbose and cumbersome at a glance, but it's actually more flexible. All classes don't just have to know about hash codes; and different containers can require different hash constraints without intruding into the contained types at all.
That's what's so good about specialization. You can hook your code into the standard library without actually affecting it. After all, you're specializing for your own types, so it's not intrusive.
Specializing
std::hash()
for your type is thus perfectly okay, so long as you do intend to use that as a reasonable default. Otherwise, you may want to consider an alias template for the combination ofstd::unordered_map
and your key type.