22

I need to define an unordered_map like this unordered_map<pair<int, int>, *Foo>, what is the syntax for defining and passing a hash and equal functions to this map?

I've tried passing to it this object:

class pairHash{
public:
    long operator()(const pair<int, int> &k) const{
        return k.first * 100 + k.second;
    }
};

and no luck:

unordered_map<pair<int, int>, int> map = unordered_map<pair<int, int>, int>(1,
*(new pairHash()));

I have no Idea what is the size_type_Buskets means so I gave it 1. What is the right way to do it? Thanks.

MoonBun
  • 4,322
  • 3
  • 37
  • 69

3 Answers3

45

This is an unfortunate omission in C++11; Boost has the answer in terms of hash_combine. Feel free to just paste it from them! Here's how I hash pairs:

template <class T>
inline void hash_combine(std::size_t & seed, const T & v)
{
  std::hash<T> hasher;
  seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}

namespace std
{
  template<typename S, typename T> struct hash<pair<S, T>>
  {
    inline size_t operator()(const pair<S, T> & v) const
    {
      size_t seed = 0;
      ::hash_combine(seed, v.first);
      ::hash_combine(seed, v.second);
      return seed;
    }
  };
}

You can use hash_combine as the basis for many other things, like tuples and ranges, so you could hash an entire (ordered) container, for example, as long as each member is individually hashable.

Now you can just declare a new map:

std::unordered_map<std::pair<int, int>, my_mapped_type> mymap;

If you want to use your homebrew hasher (which hasn't got good statistical properties), you have to specify the template parameters explicitly:

std::unordered_map<std::pair<int,int>, int, pairHash> yourmap;

Note that there's no need to specify a copy of a hasher object, as the default is to default-construct one for you.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • @Vlad: Hmm... well, with the pair hash specialization, you can just write `std::unordered_map, int> map;` and not worry any further... If you really want a named hasher, you have to specify all four template parameters, ``. – Kerrek SB Aug 28 '11 at 16:41
  • 14
    Whoa, easy on the hostility :-) Check my previous comment: if you want to specify your own hasher class, you have to explicitly name all four template parameters, which you aren't doing. I added a line on how to do that. – Kerrek SB Aug 28 '11 at 16:52
  • @Kerrek SB You got the order of the 4 template parameters wrong, the equal predicate follows the hasher. Also, I like this method better than what I have posted, but maybe Vlad has to use this custom hash function. – Praetorian Aug 28 '11 at 16:58
  • @Praetorian: Ohh, thank you, how silly -- in that case it's much easier, because you don't need to list the fourth parameter at all. – Kerrek SB Aug 28 '11 at 17:01
  • @Kerrek SB No, it seems you don't. I didn't know there was an `operator==` defined for `std::pair`. – Praetorian Aug 28 '11 at 17:03
  • @Praetorian: Yeah, `pair` is a cool class. It basically does everything you expect straight out of the box. I really don't know why the hasher wasn't also included in the new version... :-( – Kerrek SB Aug 28 '11 at 17:06
  • When I try adding the code, I get a syntax error missing ';' before '<' on the line 'template struct hash>' Am I missing something? – bpeikes Jun 26 '13 at 13:33
  • @bpeikes: Try leaving a space between the angled brackets. – Kerrek SB Jun 26 '13 at 16:57
  • Thanks Kerrek, turned out that the error was because the version of VS I was compiling with had issues with "hash" I had to change it to std::tr1::hash. – bpeikes Jun 27 '13 at 20:42
  • Isn't specializing in `std` for a type also in `std` illegal? It really *should* be... – Yakk - Adam Nevraumont Sep 09 '13 at 13:42
  • @Yakk: No. You're explicitly allowed and encouraged to add specializations to `std` (but not *overloads*!), as long as you adhere to various contracts laid out by the standard. – Kerrek SB Sep 09 '13 at 15:37
  • @KerrekSB Add specializations to `std` for your own types: but for types in `std`? That just seems like you are aching to interfere with the next version of the library. Aha, here it is: "A program may add a template specialization for any standard library template to namespace std _only if the declaration depends on a user-defined type_ and the specialization meets the standard library requirements for the original template and is not explicitly prohibited." -- your specialization does not depend on a user-defined type, so is prohibited. Punchline? That was from one of your own answers! – Yakk - Adam Nevraumont Sep 09 '13 at 15:44
10

If you are fine with using Boost, a cleaner solution is to rely on Boost's implementation of the hash function for pairs (which in fact does exactly what kerrek-sb explains in his answer). Therefore all you have to do is:

#include <unordered_map>
#include <boost/functional/hash.hpp>

using namespace std;
using namespace boost;

unordered_map<pair<int, int>, int, hash<pair<int, int>>> table;
Paul Baltescu
  • 2,095
  • 4
  • 25
  • 30
  • A good solution, but I wouldn't use the namespace. I would write it out. It's pretty important here what comes from which namespace. – petersohn Jun 23 '15 at 14:52
  • I also very appreciate this answer and I am not really sure whether the top answer should be considered a good practice. Defining such home-brew helper functions only pollutes code imho. – Jendas Oct 31 '15 at 14:46
10

The return type of the hash function should be size_t, not long (although this is not the cause of the error). The syntax you've shown for providing a custom hash function is incorrect.

You'll also need to provide an equal predicate to make the above work properly.

#include <unordered_map>
#include <utility>

using namespace std;

class pairHash{
public:
    size_t operator()(const pair<int, int> &k) const{
        return k.first * 100 + k.second;
    }
};

struct pairEquals : binary_function<const pair<int,int>&, const pair<int,int>&, bool> {
  result_type operator()( first_argument_type lhs, second_argument_type rhs ) const
  {
    return (lhs.first == rhs.first) && (lhs.second == rhs.second);
  }
};     

int main()
{
  unordered_map<pair<int, int>, int, pairHash, pairEquals> myMap;

  myMap[make_pair(10,20)] = 100;
  myMap.insert( make_pair(make_pair(100,200), 1000) );
}

EDIT:
You don't need to define the equal predicate since operator== is defined for std::pair and it does exactly what I've done in pairEquals. You'll only need the pairEquals definition if you expect the comparison to be done differently.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • 2
    Equality for pairs is already defined by default, as is lexicographic ordering... it's really quite a handy little class :-) – Kerrek SB Aug 28 '11 at 17:02
  • binary_function seems to be deprecated as of C++ 11 - http://www.cplusplus.com/reference/functional/binary_function/ – Gautham Mar 19 '18 at 16:33