Search code examples
c++boosthashnamespaces

Why do I have to define a hash function for each namespace as the unordered_set?


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").


Solution

  • 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);
    }