I am trying to build an std::unordered_map
with a custom type as a key. The custom type is a simple std::vector<double>
. The idea is that it will function as a convenient container for 2D points on a grid. Everything is working correctly except for outputting the hashed key. Here is a sample I put together to illustrate the idea:
#include <iostream>
#include <vector>
#include <unordered_map>
#include <boost/functional/hash.hpp>
#include <chrono>
namespace std
{
template<typename Container>
struct hash {
std::size_t operator()(Container const& v) const
{
return boost::hash_range(v.begin(), v.end());
}
};
}
int main()
{
std::unordered_map<std::vector<double>, double> test;
unsigned long t = (unsigned long) std::chrono::system_clock::now().time_since_epoch().count();
std::srand(t);
for (uint i = 0; i < 100 ; ++i)
{
double d1 = i/200.0;
double d2 = i/200.0;
std::vector<double> v({d1, d2});
test[v] = d1;
}
std::cout << "Size:" << test.size() << std::endl;
for (const auto& it : test )
{
std::cout << it.first << ":" << it.second << std::endl;
}
return 0;
}
The hash specialisation template is courtesy of another SO thread. The trouble is that g++ spits out the following error when I try to compile the above:
cannot bind 'std::ostream {aka std::basic_ostream<char>}' lvalue to 'std::basic_ostream<char>&&'
std::cout << it.first << ":" << it.second << std::endl;
^
It is obvious that it stumbles on it.first
. The code compiles and runs correctly if I remove it.first
. I understand that the output will not be a vector of doubles. I did look around SO for quite a while, but I couldn't find a definitive answer on how to std::cout
the hash value from an unordered map with a custom key type. Any feedback will be highly appreciated.
Thank you in advance!
Edit:
Thank you everyone for your input. This was my first encounter with non-primitive types as hashed keys, so I had the wrong idea about how the key/value pairs were stored (I assumed that the hashed value is the key, whereas in fact it is the actual custom type).
The value_type
of an unordered_map<K,V>
is pair<const K, V>
. That's what you get when you iterate over it with a range-for. There's no operator<<
overload for vector
s, causing the error you see.
namespace std
{
template<typename Container>
struct hash {
std::size_t operator()(Container const& v) const
{
return boost::hash_range(v.begin(), v.end());
}
};
}
This isn't a specialization of std::hash
. It's a redefinition of the primary template, which in your case only compiled by pure happenstance. (The implementation will have to leave the primary std::hash
template undefined, and will have to actually declare hash
in the std
namespace and not an inlined namespace. Your code breaks up completely on libc++ , for example.)
A specialization would look like
namespace std
{
// full specialization
template<>
struct hash<Foo> {
// ^^^^^
std::size_t operator()(Foo const& v) const
{
// ...
}
};
// partial specialization
template<typename T>
struct hash<Bar<T>>{
// ^^^^^^^^
std::size_t operator()(Bar<T> const& v) const
{
// ...
}
};
}
Note the explicit template argument list following hash
. That's the indication that this is a specialization.
It is illegal to specialize std::hash
for std::vector<double>
anyway, because it doesn't depend on a user-defined type. Writing your own hasher is easy:
struct container_hasher {
template<typename Container>
std::size_t operator()(Container const& v) const
{
using std::begin;
using std::end;
return boost::hash_range(begin(v), end(v));
}
};
Note that I templated the operator()
instead of the type itself - this makes writing the hasher type easier. The using
followed by an unqualified call enables ADL for begin
and end
.
And then the definition of test
becomes
std::unordered_map<std::vector<double>, double, container_hasher> test;
As far as I am aware, there is no standard interface for exposing the hash value (as opposed to the hashing function) from std::unordered_map
.
As you have seen, dereferencing a std::unordered_map<Key,V>::iterator
yields something convertible to std::unordered_map<Key,V>::value_type
, which in turn is a std::pair<const Key,V>
representing a (key,value) pair, rather than a (hashed key,value) pair.
Correspondingly, it.first
is giving you a std::vector<double>
rather than a std::size_t
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.