Why can't I compile an unordered_map with a pair as key?

C++DictionaryUnordered MapKeyvaluepair

C++ Problem Overview


I am trying to create an unordered_map to map pairs with integers:

#include <unordered_map>

using namespace std;
using Vote = pair<string, string>;
using Unordered_map = unordered_map<Vote, int>;

I have a class where I have declared an Unordered_map as a private member.

However, I am getting the following error when I try to compile it:

> /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/type_traits:948:38: Implicit instantiation of undefined template 'std::__1::hash, std::__1::basic_string > >'

I am not getting this error if I use a regular map like map<pair<string, string>, int> instead of an unordered_map.

Is it not possible to use pair as key in unordered maps?

C++ Solutions


Solution 1 - C++

You need to provide a suitable hash function for your key type. A simple example:

#include <unordered_map>
#include <functional>
#include <string>
#include <utility>

// Only for pairs of std::hash-able types for simplicity.
// You can of course template this struct to allow other hash functions
struct pair_hash {
    template <class T1, class T2>
    std::size_t operator () (const std::pair<T1,T2> &p) const {
        auto h1 = std::hash<T1>{}(p.first);
        auto h2 = std::hash<T2>{}(p.second);

        // Mainly for demonstration purposes, i.e. works but is overly simple
        // In the real world, use sth. like boost.hash_combine
        return h1 ^ h2;  
    }
};

using Vote = std::pair<std::string, std::string>;
using Unordered_map = std::unordered_map<Vote, int, pair_hash>;

int main() {
    Unordered_map um;
}

This will work, but not have the best hash-properties. You might want to have a look at something like boost.hash_combine for higher quality results when combining the hashes. This is also discussed in greater detail – including the aforementioned solution from boost – in this answer.

For real world use: Boost also provides the function set hash_value which already provides a hash function for std::pair, as well as std::tuple and most standard containers.


More precisely, it will produce too many collisions. E.g., every symmetric pair will hash to 0 and pairs that differ only by permutation will have the same hash. This is probably fine for your programming exercise, but might seriously hurt performance of real world code.

Solution 2 - C++

My preferred way of solving this problem is to define a key function that transforms your pair into a unique integer (or any hashable data type). This key is not the hash key. It is the unique ID of the pair of data that will then be optimally hashed by the unordered_map. For example, you wanted to define an unordered_map of the type

  unordered_map<pair<int,int>,double> Map;

And you want to use Map[make_pair(i,j)]=value or Map.find(make_pair(i,j)) to operate on the map. Then you'll have to tell the system how to hash a pair of integers make_pair(i,j). Instead of that, we can define

  inline size_t key(int i,int j) {return (size_t) i << 32 | (unsigned int) j;}

and then change the type of the map to

  unordered_map<size_t,double> Map;

We can now use Map[key(i,j)]=value or Map.find(key(i,j)) to operate on the map. Every make_pair now becomes calling the inline key function.

This method guarantees that the key will be optimally hashed, because now the hashing part is done by the system, which will always choose the internal hash table size to be prime to make sure every bucket is equally likely. But you have to make yourself 100% sure that the key is unique for every pair, i.e., no two distinct pairs can have the same key, or there can be very difficult bugs to find.

Solution 3 - C++

If using pair is not a strict requirement, you can simply use map twice.

#include <unordered_map>

using namespace std;
using Unordered_map = unordered_map<string, unordered_map<string, int>>;

Unordered_map um;
um["Region1"]["Candidate1"] = 10;
cout << um["Region1"]["Candidate1"];    // 10

Solution 4 - C++

For pair key, we can use boost pair hash function:

#include <iostream>
#include <boost/functional/hash.hpp>
#include <unordered_map>
using namespace std;

int main() {
  unordered_map<pair<string, string>, int, boost::hash<pair<string, string>>> m;
  
  m[make_pair("123", "456")] = 1;
  cout << m[make_pair("123", "456")] << endl;
  return 0;
}

Similarly we can use boost hash for vectors,

#include <iostream>
#include <boost/functional/hash.hpp>
#include <unordered_map>
#include <vector>
using namespace std;

int main() {
  unordered_map<vector<string>, int, boost::hash<vector<string>>> m;
  vector<string> a({"123", "456"});
  
  m[a] = 1;
  cout << m[a] << endl;
  return 0;
}

Solution 5 - C++

Reference: C++ Standard Library: A tutorial and reference, Second version Chapter 7.9.2: Creating and Controlling unordered Container

All solutions I found in Google use XOR to generate hashcode of pair, which is totally bad. see why-is-xor-the-default-way-to-combine-hashes. However, the book has given us the best solution, using hash_combine, which is taken from Boost. The solution is much better than XOR when I tested it in Online Judge(Atcoder). I organized the code as a template as follow. You can copy and paste it as much as you can. And it is convenient to change it to fit any custom struct/class.

Update: add hash template for tuple.

#include <functional>

namespace hash_tuple {
template <typename TT> struct hash {
    size_t operator()(TT const &tt) const { return std::hash<TT>()(tt); }
};

// from boost (functional/hash):
// see http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html template
template <class T> inline void hash_combine(std::size_t &seed, T const &v) {
    seed ^= hash_tuple::hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}

// Recursive template code derived from Matthieu M.
template <class Tuple, size_t Index = std::tuple_size<Tuple>::value - 1>
struct HashValueImpl {
    void operator()(size_t &seed, Tuple const &tuple) const {
        HashValueImpl<Tuple, Index - 1>{}(seed, tuple);
        hash_combine(seed, std::get<Index>(tuple));
    }
};
template <class Tuple> struct HashValueImpl<Tuple, 0> {
    void operator()(size_t &seed, Tuple const &tuple) const {
        hash_combine(seed, std::get<0>(tuple));
    }
};

template <typename... TT> struct hash<std::tuple<TT...>> {
    size_t operator()(std::tuple<TT...> const &tt) const {
        size_t seed = 0;
        HashValueImpl<std::tuple<TT...>>{}(seed, tt);
        return seed;
    }
};
// auxiliary generic functions to create a hash value using a seed
template <typename T> inline void hash_val(std::size_t &seed, const T &val) {
    hash_combine(seed, val);
}

template <typename T, typename... Types>
inline void hash_val(std::size_t &seed, const T &val, const Types &... args) {
    hash_combine(seed, val);
    hash_val(seed, args...);
}

template <typename... Types>
inline std::size_t hash_val(const Types &... args) {
    std::size_t seed = 0;
    hash_val(seed, args...);
    return seed;
}

struct pair_hash {
    template <class T1, class T2>
    std::size_t operator()(const std::pair<T1, T2> &p) const {
        return hash_val(p.first, p.second);
    }
};
} // namespace hash_tuple

#include <bits/stdc++.h>

int main() {
    using ll = long long;
    // std::unordered_map<std::pair<ll, ll>, ll, hash_tuple::pair_hash>
    // hashmapPair; std::unordered_set<std::pair<ll, ll>, hash_tuple::pair_hash>
    // hashsetPair;

    std::unordered_map<std::pair<ll, ll>, ll, hash_tuple::pair_hash>
        hashmapPair;
    hashmapPair[{0, 0}] = 10;
    std::unordered_set<std::pair<ll, ll>, hash_tuple::pair_hash> hashsetPair;
    hashsetPair.insert({1, 1});

    using TI = std::tuple<ll, ll, ll, ll>;
    std::unordered_map<TI, ll, hash_tuple::hash<TI>> hashmapTuple;
    hashmapTuple[{0, 1, 2, 3}] = 10;
    std::unordered_set<TI, hash_tuple::hash<TI>> hashsetTuple;
    hashsetTuple.emplace(0, 1, 2, 3);

    return 0;
}

Solution 6 - C++

As your compilation error indicates, there is no valid instantiation of std::hash<std::pair<std::string, std::string>> in your std namespace.

According to my compiler:

> Error C2338 The C++ Standard doesn't provide a hash for this > type. c:\program files (x86)\microsoft visual studio > 14.0\vc\include\xstddef 381

You can provide your own specialization for std::hash<Vote> as follows:

#include <string>
#include <unordered_map>
#include <functional>

using namespace std;
using Vote = pair<string, string>;
using Unordered_map = unordered_map<Vote, int>;

namespace std
{
	template<>
	struct hash<Vote>
	{
		size_t operator()(Vote const& v) const
		{
			// ... hash function here ...
		}
	};
}

int main()
{
    Unordered_map m;
}

Solution 7 - C++

In the comments on the answer by Baum mit Augen, the user Joe Black asked for an example on using a lambda expressions instead of defining a hash function. I agree with the opinion of Baum mit Augen, that this might harm readability, especially if you want to implement a more universal solution. Therefore, I'd like to keep my example short by focusing on a specific solution for std::pair<std::string, std::string>, as presented by the OP. The example also uses a handcrafted combination of std::hash<std::string> function calls:

using Vote = std::pair<std::string, std::string>;
auto hash = [](const Vote& v){
    return std::hash<std::string>()(v.first) * 31 + std::hash<std::string>()(v.second);
};
using Unordered_map = std::unordered_map<Vote, int, decltype(hash)>;
Unordered_map um(8, hash);

Code on Ideone

Solution 8 - C++

There is a hack to such problems

Use a std:unordered_map of string

Look at the following example-

I am required to hash the endpoint(corner) of a rectangle >Error Approach

unordered_map<pair<int, int>, int> M;           //ERROR

pair<int, int> p;
M[p]++;

> Hack

unordered_map<string, int> M;

pair<int, int> p;
string s = to_string(p.first) + "_" + to_string(p.second);
M[s]++;

Such hack even works if you are required to create a hash of decimal or double as a key :)

Solution 9 - C++

I've simplified @YoungForest's answer to only work with pairs (= not with arbitrary length tuples) as was requested by the OP. I've also minimized the boilerplate code:

#include <functional>
#include <iostream>
#include <unordered_map>
#include <utility>       # pair

using namespace std;

// from boost (functional/hash):
// see http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html template
template <class T> inline void hash_combine(size_t &seed, T const &v) {
    seed ^= hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}

struct pair_hash {
    template <class T1, class T2>
    size_t operator()(const pair<T1, T2> &p) const {
        size_t seed = 0;
        hash_combine(seed, p.first);
        hash_combine(seed, p.second);
        return seed;
    }
};

int main() {
    unordered_map<pair<int, int>, int, pair_hash> d;
    d[{1, 2}] = 3;
    cout << d.find({1, 2})->second << endl;

    return 0;
}

It uses the same logic as in the boost library (that is better than the xor version).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMarreView Question on Stackoverflow
Solution 1 - C++Baum mit AugenView Answer on Stackoverflow
Solution 2 - C++Zhuoran HeView Answer on Stackoverflow
Solution 3 - C++EfreetoView Answer on Stackoverflow
Solution 4 - C++Felix GuoView Answer on Stackoverflow
Solution 5 - C++YoungForestView Answer on Stackoverflow
Solution 6 - C++bku_dryttView Answer on Stackoverflow
Solution 7 - C++honkView Answer on Stackoverflow
Solution 8 - C++H SooraView Answer on Stackoverflow
Solution 9 - C++Antony HatchkinsView Answer on Stackoverflow