1

From http://en.cppreference.com/w/cpp/container/unordered_map/unordered_map, unordered_map can use lambda functions for hashing function. It is also answered in the following: How to use lambda function as hash function in unordered_map?

My question is about hashing a struct which includes a container, say a vector. Since cpprefence has the following code example of

#include <algorithm>
#include <cassert>
#include <string>
#include <unordered_set>
#include <vector>
#include <unordered_map>

using std::hash;
using std::string;
using std::unordered_set;
using std::vector;
int main(int argc, char* argv[]) {
    struct Goo {int val; };
    auto hash = [](const Goo &g){ return std::hash<int>{}(g.val); };
    auto comp = [](const Goo &l, const Goo &r){ return l.val == r.val; };
    std::unordered_map<Goo, double, decltype(hash), decltype(comp)> m8(10, hash, comp);

    return 0;
}

I have modified it so that it tries to use vector<int> in the Goo.

#include <algorithm>
#include <cassert>
#include <string>
#include <unordered_set>
#include <vector>
#include <unordered_map>

using std::hash;
using std::string;
using std::unordered_set;
using std::vector;

int main() {
    using vint = std::vector<int>;
    struct Goo { vint v; };
    auto hash = [](const Goo &g){ 
        std::size_t hash_value = 0;
        for (const int& i : g.v) {
            hash_value ^= std::hash<int>{}(i);
        }
        return hash_value;
    };
    auto comp = [](const Goo &l, const Goo &r){
        return unordered_set<int>(l.v.begin(), l.v.end()) ==
               unordered_set<int>(r.v.begin(), r.v.end());
    };
    vint here;
    std::unordered_map<Goo, double, decltype(hash), decltype(comp)> 
                                            m8(here,0, hash, comp);
    return 0;
}

This code doesn't compile. The compiler complains about not no matching function for call to ‘std::unordered_map<main(int, char**)::Goo. The number of arguments seems to be not the issue, but something must be working not correctly. I would greatly appreciate your guidance.

I am using g++ -std=c++17 by the way.

user7865286
  • 344
  • 2
  • 11

2 Answers2

1

I think that you didn't understand the example. This line:

std::unordered_map<Goo, double, decltype(hash), decltype(comp)> m8(10, hash, comp);

is responsible for creating unordered_map with at least 10 buckets and provided hash and comp functions. It does not create any unordered_map with 10 elements. Therefore, your code should look like this:

using vint = std::vector<int>;
struct Goo { vint v; };
auto hash = [](const Goo &g){ 
    std::size_t hash_value = 0;
    for (const int& i : g.v) {
        hash_value ^= std::hash<int>{}(i);
    }
    return hash_value;
};
auto comp = [](const Goo &l, const Goo &r){
    return std::unordered_set<int>(l.v.begin(), l.v.end()) ==
        std::unordered_set<int>(r.v.begin(), r.v.end());
};
std::unordered_map<Goo, double, decltype(hash), decltype(comp)>
    m8(10, hash, comp);

unordered_map simply does not have any constructor that will mach this:

std::unordered_map<Goo, double, decltype(hash), decltype(comp)>
    m8(here, 0, hash, comp);
pptaszni
  • 5,591
  • 5
  • 27
  • 43
0

I suggest using the composable hashing infrastructure from N3980 Types Don't Know #. It also saves you from error-prone manual hash combining.

An example of hashing a structure with two members, one of which is a vector:

// Hashing infrastructure begin.

class fnv1a
{
    std::size_t state_ = 14695981039346656037u;

public:
    using result_type = std::size_t;

    void operator()(void const* key, std::size_t len) noexcept {
        unsigned char const* p = static_cast<unsigned char const*>(key);
        unsigned char const* const e = p + len;
        for (; p < e; ++p)
            state_ = (state_ ^ *p) * 1099511628211u;
    }

    explicit operator result_type() noexcept {
        return state_;
    }
};

template<class HashAlgorithm>
struct uhash
{
    using result_type = typename HashAlgorithm::result_type;

    template <class T>
    result_type operator()(T const& t) const noexcept {
        HashAlgorithm h;
        hash_append(h, t);
        return static_cast<result_type>(h);
    }
};

template<class HashAlgorithm, class T>
typename std::enable_if<std::is_integral<T>::value>::type
inline hash_append(HashAlgorithm& h, T const& t) noexcept {
    h(&t, sizeof t);
}

template<class HashAlgorithm, class T, class... Args>
void hash_append(HashAlgorithm& h, std::vector<T, Args...> const& v) noexcept {
    for(auto const& t : v)
        hash_append(h, t);
    hash_append(h, v.size());
}

template<class HashAlgorithm, class T, class... Args>
void hash_append(HashAlgorithm& h, std::unordered_map<T, Args...> const& v) noexcept {
    for(auto const& t : v)
        hash_append(h, t);
    hash_append(h, v.size());
}

// Hashing infrastructure end

struct Goo
{
    std::vector<int> a;
    int b;

    template<class HashAlgorithm>
    friend void hash_append(HashAlgorithm& h, Goo const& v) noexcept {
        hash_append(h, v.a);
        hash_append(h, v.b);
    }

    bool operator==(Goo const& other) const {
        return a == other.a && b == other.b;
    }
};

int main() {
    std::unordered_map<Goo, double, uhash<fnv1a>> m;
    m[Goo{{1,2,3}, 1}] = 1;
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271