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?
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.
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.
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.
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;
}
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;
}
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
I know this is too naive implementation compared to other answers, but there is a workaround.
If you are taking the input of pairs, just hash the pair with another integer in an unordered_hash while taking input and then use this integral value indirectly to hash the pair, i.e.
unordered_hash<int, Vote> h;
using Unordered_map = unordered_map<i, int>; // i is corresponding value to pair
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);
来源:https://stackoverflow.com/questions/32685540/why-cant-i-compile-an-unordered-map-with-a-pair-as-key