Code Warrior
Code Warrior

Reputation: 133

User-defined hash function for unordered_map

I am trying to create a templated wrapper class on stl unordered_map. I am passing the hash function class as a template parameter to the wrapper class and provided a string specialization. The below code compiles and works but if the commented part is included then compilation error saying:

"/usr/include/c++/6/bits/unordered_map.h:143:28: error: no matching function for call to ‘HashFunction

::HashFunction()’ const hasher& __hf = hasher(),".

However, I am bound to have the ctor of the hash function class. I tried various ways but could not make it work. Please provide your thoughts/comments.

#include <iostream>
#include <unordered_map>
using namespace std;

template< class Key >
class HashFunction
{
  public:
    //HashFunction( const Key & inKey );
    size_t operator()(const Key &inKey) const;
  private:
    unsigned mHashCode;
};

//template<>
//HashFunction< string >::HashFunction( const string & inKey )
//{
    //mHashCode=std::hash<string>{}(inKey);
//}

template <> 
size_t HashFunction< string >::operator()(const string &inKey) const 
{
  return std::hash<string>{}(inKey);
  //return mHashCode;
}

template< class Key, class Val, class Hash = HashFunction< Key > >
class unordered_map_wrapper
{
  public:
   unordered_map_wrapper();
  private:
    unordered_map<Key, Val, Hash> * mTable;
};

template< class Key, class Val, class Hash >
unordered_map_wrapper< Key, Val, Hash >::unordered_map_wrapper()
{
    mTable=new unordered_map<Key, Val, Hash>(10);
}

int main() {
    unordered_map_wrapper<string, unsigned> h;
    return 0;
}

Upvotes: 1

Views: 8686

Answers (2)

Aconcagua
Aconcagua

Reputation: 25536

This is not how the Hash template parameter is intended to work in std::unordered_map! The map creates one single Hash instance (using the default constructor, so you need to provide it!) which is used for all hash calculations needed further. So your class must look like this instead:

template<class Key>
class HashFunction
{
public:
    // HashFunction(); <- can just leave it out...
    size_t operator()(const Key& inKey) const;
};

template<>
size_t HashFunction<std::string>::operator()(const std::string& inKey) const
{
    // calculate hash value in operator!!!
    return std::hash<std::string>()(inKey);
}

To avoid constructing the std::hash all the time, though, I'd rather specialise the whole template class instead:

template<class Key>
class HashFunction; // leave entirely unimplemented
                    // (if it has no general meaning, at least...)

template<>
class HashFunction<std::string>
{
public:
    size_t operator()(const std::string& inKey) const
    {
        return mHash(inKey);
    }
private:
    std::hash<std::string> mHash;
};

It does not make much sense re-implementing something that is already there, though. May I assume that you used std::string here just as a place holder for your own class?

By the way: A simple typedef would do the job easier than your wrapper class:

template <typename Key, typename Value>
using my_unordered_map = std::unordered_map<Key, Value, HashFunction<Key>>;

Maybe you are interested in alternatives, too? Lets first assume you have two classes (instead of std::string):

class C1
{ };
class C2
{ };

Most elegant solution (in my eyes at least) is simply specialising std::hash for your classes (while in general, it is illegal to add something to std namespace, template specialisations are the exception...):

namespace std
{
template<>
class hash<C1>
{
public:
    size_t operator()(C const& c) const
    {
        return 0; // your implementation here...
    }
};
// same for C2
}

// no typedef necessary, just use std::unordered_map directly:
std::unordered_map<C1, unsigned> m1;
std::unordered_map<C2, unsigned> m2;

You can provide your own HashFunction class, providing overloads:

class HashFunction
{
public:
    size_t operator()(C1 const& c) const
    {
        return 0;
    }
    size_t operator()(C2 const& c) const
    {
        return 0;
    }
};

Again, a typedef makes your life easier:

template <typename Key, typename Value>
using my_unordered_map = std::unordered_map<Key, Value, HashFunction>;

std::unordered_map<C1, unsigned, HashFunction> m1;
my_unordered_map<C1, unsigned> m1_;

std::unordered_map<C2, unsigned, HashFunction> m2;
my_unordered_map<C2, unsigned> m2_;

Finally, if you absolutely need to initialise your HashFunction with some parameters to configure it correctly, you can do so, but you have to deliver a pre-configured instance to your hash map then!

template<typename T>
class HashFunction
{
public:
    HashFunction(double value);
    size_t operator()(T const& c) const;
private:
    double data;
};

template<typename T>
HashFunction<T>::HashFunction(double value)
        : data(value)
{ }

template<>
size_t HashFunction<C1>::operator()(C1 const& c) const
{
    return static_cast<size_t>(data);
};
template<>
size_t HashFunction<C2>::operator()(C2 const& c) const
{
    return static_cast<size_t>(data);
};

template <typename Key, typename Value>
using my_unordered_map = std::unordered_map<Key, Value, HashFunction<Key>>;

my_unordered_map<C1, unsigned> m1(12, HashFunction<C1>(10.12));
my_unordered_map<C2, unsigned> m2(10, HashFunction<C2>(12.10));

Well, now, at least, your wrapper class can come in handy again:

template <typename Key, typename Value, typename Hash = HashFunction<Key>>
class unordered_map_wrapper
{
public:
    unordered_map_wrapper()
        : mMap(16, Hash())
    { }
    unordered_map_wrapper(double parameter)
        : mMap(16, Hash(parameter))
    { }
private:
    std::unordered_map<Key, Value, Hash> mMap;
};

unordered_map_wrapper<C1, unsigned> m1(10.12);
unordered_map_wrapper<C2, unsigned> m2(12.10);
// possible due to provided default ctor:
unordered_map_wrapper<std::string, unsigned, std::hash<std::string>> m3;

Upvotes: 9

didiz
didiz

Reputation: 1099

It expects to have a default constructor, which is removed when you define your custom version.

This compiles:

template< class Key >
class HashFunction
{
public:
    HashFunction(){};
    HashFunction( const Key & inKey );
    size_t operator()(const Key &inKey) const;
private:
    unsigned mHashCode;
};

Upvotes: 1

Related Questions