2

I wanted to make a hash function for a class I am writing and I wanted to make the hash function a friend of the class so that I didn't have to write an unnecessary getter method. To do that I followed the accepted answer in this SO post. But I wanted to be able to insert the objects into either std::unordered_set or boost::unordered_set. So I wrote it like this:

#include <iostream>
#include <unordered_set>
#include <boost/functional/hash.hpp>
#include <boost/unordered_set.hpp>

class Vec;
namespace std { // line [c]
    template<>
    struct hash<Vec> {
    public:
        size_t operator()(const Vec &v) const;
    };
}

class Vec {
private:
    std::vector<int> v;
public:
    friend size_t std::hash<Vec>::operator ()(const Vec& v) const; // line [d]
    friend bool operator == (const Vec& lhs, const Vec& rhs) { return lhs.v == rhs.v; }
};

namespace std { // line [e]
    size_t hash<Vec>::operator()(const Vec &v) const {
        return boost::hash<std::vector<int> >()(v.v);
    }
}

int main() {
    Vec v;
    std::unordered_set<Vec> s1; // line [f]
    s1.insert(v); // line [g]
    boost::unordered_set<Vec> s2; // line [a]
    s2.insert(v); // line [b]
}

But I found that I had a long list of errors when trying to compile this. Then when I removed lines [a,b], it compiled and ran as expected. Then, instead of removing lines [a,b], I (1) left them in, (2) removed lines [f,g], and (3) changed lines [c,d,e] to say boost instead of std, again the code would compile properly. Finally, I tried making a duplicate declaration of the hash struct in the boost namespace:

#include <iostream>
#include <unordered_set>
#include <boost/functional/hash.hpp>
#include <boost/unordered_set.hpp>

class Vec;
namespace std {
    template<>
    struct hash<Vec> {
    public:
        size_t operator()(const Vec &v) const;
    };
}
// new: {
namespace boost {
    template<>
    struct hash<Vec> {
    public:
        size_t operator()(const Vec &v) const;
    };
}
// }

class Vec {
private:
    std::vector<int> v;
public:
    friend size_t std::hash<Vec>::operator ()(const Vec& v) const;
    // new: {
    friend size_t boost::hash<Vec>::operator ()(const Vec& v) const;
    // }
    friend bool operator == (const Vec& lhs, const Vec& rhs) { return lhs.v == rhs.v; }
};

namespace std {
    size_t hash<Vec>::operator()(const Vec &v) const {
        return boost::hash<std::vector<int> >()(v.v);
    }
}
// new: {
namespace boost {
    size_t hash<Vec>::operator()(const Vec &v) const {
        return boost::hash<std::vector<int> >()(v.v);
    }
}
// }

int main() {
    Vec v;
    std::unordered_set<Vec> s1;
    s1.insert(v);
    boost::unordered_set<Vec> s2;
    s2.insert(v);
}

My question is: why do I have to make a hash function in both the std and boost namespace to get it to work? I would say that I have an intuition for why, but I would like a very detailed explanation. And I would like any alternative solutions that would fix the fact that there is a lot of duplicate code in the above code segment (but not something like boost::unordered_set<Vec, my_vec_class_hash> because I want it to be "automatic").

redmoncoreyl
  • 133
  • 9
  • 2
    Personally, I would not specialize either `boost::` or `std::` and instead write class called `VecHasher` and then use a `whatever::unordered_set` – NathanOliver Apr 21 '21 at 19:31
  • You can even use an alias for that like `using unordered_vec_set = whatever::unordered_set;` – NathanOliver Apr 21 '21 at 19:33
  • 1
    Why would there ever be a need to use both boost and std templates for an unordered set? Sounds like an XY problem. – Sam Varshavchik Apr 21 '21 at 19:41
  • @NathanOliver Yeah, I did know that was an option. I just don't know what the standard way to do this is. Your way would eliminate duplicate code, but is it standard practice to provide a separate hashing class for all your classes? I want this to be presentable and look like I know what I'm doing lol. – redmoncoreyl Apr 21 '21 at 19:42
  • @SamVarshavchik Admittedly any situation that would require this would be quite contrived. – redmoncoreyl Apr 21 '21 at 19:47
  • 2
    I don't know about boost but specializing `std::hash` is explicitly [allowed](https://timsong-cpp.github.io/cppwp/n4659/unord.hash#2) by the standard. It's a nice and concise way, there's nothing wrong with it. – rustyx Apr 21 '21 at 19:53
  • In terms of alternative, you could definitely do this through macro. However, I would not recommend this unless you really have a lot of thing that need to be done in similar way. – Ranoiaetep Apr 21 '21 at 21:26
  • 1
    @Ranoiaetep no need, see my answer. – sehe Apr 22 '21 at 14:07

1 Answers1

2

You can reduce the clutter a long way, by using Boost's ADL-enabled customization point hash_value:

class Vec {
  private:
    std::vector<int> v;

    friend size_t hash_value(const Vec& v) {
        return boost::hash_range(begin(v.v), end(v.v));
    }
    friend bool operator==(const Vec& lhs, const Vec& rhs) {
        return lhs.v == rhs.v;
    }
};

In fact, the hash function can be even simpler with return boost::hash_value(v.v); in this case.

This is already enough to make Boost's unordered containers work with your type:

boost::unordered_set<Vec> s2;
s2.insert(v);

Adding std support

That's a non-issue now:

template <> struct std::hash<Vec> : boost::hash<Vec> {};

Live Demo

Live On Coliru

#include <boost/functional/hash.hpp>
#include <boost/unordered_set.hpp>
#include <iostream>
#include <unordered_set>

class Vec {
  private:
    std::vector<int> v;

    friend size_t hash_value(const Vec& v) {
        return boost::hash_value(v.v);
        //return boost::hash_range(begin(v.v), end(v.v));
    }
    friend bool operator==(const Vec& lhs, const Vec& rhs) {
        return lhs.v == rhs.v;
    }
};

template <> struct std::hash<Vec> : boost::hash<Vec> {};

int main() {
    Vec v;
    std::unordered_set<Vec> s1;
    s1.insert(v);
    boost::unordered_set<Vec> s2;
    s2.insert(v);
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • 1
    Yes that works great! It took me some time to figure out how to separate the class into header and implementation files, then I realized I needed to put the `std::hash` initialization in the header. – redmoncoreyl Apr 22 '21 at 15:28
  • 1
    Yup. That's called a definition, in particular a definition of a specialization. Maybe you understood this, but in general template definitions need to be visible in all places that may instantiate them, so [need to be in the headers](https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file#:~:text=Templates%20must%20be%20used%20in,given%2Fdeduced%20for%20template%20parameters.&text=When%20you%20compile%20a%20non,compiling%20a%20concrete%20function%2Fclass.) – sehe Apr 22 '21 at 15:33