Reputation: 201
I searched many pages, and I think I have known how to write the std::hash. But I don't know where to put it.
An example is presented here http://en.cppreference.com/w/cpp/utility/hash .
However, I defined my type Instance
in namespace ca
in file instance_management.h
. I want to use unordered_set<Instance>
in the same file in another class InstanceManager
. So I write the following code:
namespace std
{
template <> struct hash<ca::Instance>
{
size_t operator()(const ca::Instance & instance) const
{
std::size_t seed = 0;
// Some hash value calculation here.
return seed;
}
};
} // namespace std
But where should I put it? I tried many locations but all failed.
I am using visual studio 2013. I tried to put the previous code in some locations but all failed to compile it.
// location 1
namespace ca
{
class Instance {...}
class InstanceManager
{
// ... some other things.
private unordered_set<Instance>;
}
}
// location 2
Upvotes: 4
Views: 3026
Reputation: 70526
There are several ways.
std::hash
In your code make sure that your std::hash<Instance>
specialization is preceded immediately by the Instance
class definition, and followed by the use of the unordered_set
container that uses it.
namespace ca
{
class Instance {...};
}
namespaces std {
template<> hash<Instance> { ... };
}
namespace ca {
class InstanceManager
{
// ... some other things.
private unordered_set<Instance>;
}
}
One drawback is that you can have funny name lookup interference when passing a std::hash<ca::Instance>
to other functions. The reason is that the associated namespace (ca
) of all the template arguments of std::hash
can be used during name lookup (ADL). Such errors are a bit rare, but if they occur they can be hard to debug.
See this Q&A for more details.
unordered_set
struct MyInstanceHash { ... };
using MyUnorderedSet = std:unordered_set<Instance, MyInstanceHash>;
Here, you simply pass your own hash function to the container and be done with it. The drawback is that you have to explicitly type your own container.
hash_append
Note, however, there is the N3980 Standard proposal is currently pending for review. This proposal features a much superior design that uses a universal hash function that takes an arbitrary byte stream to be hashed by its template parameter (the actual hashing algorithm)
template <class HashAlgorithm>
struct uhash
{
using result_type = typename HashAlgorithm::result_type;
template <class T>
result_type
operator()(T const& t) const noexcept
{
HashAlgorithm h;
using std::hash_append;
hash_append(h, t);
return static_cast<result_type>(h);
}
};
A user-defined class X then has to provide the proper hash_append
through which it presents itself as a byte stream, ready to be hashed by the univeral hasher.
class X
{
std::tuple<short, unsigned char, unsigned char> date_;
std::vector<std::pair<int, int>> data_;
public:
// ...
friend bool operator==(X const& x, X const& y)
{
return std::tie(x.date_, x.data_) == std::tie(y.date_, y.data_);
}
// Hook into the system like this
template <class HashAlgorithm>
friend void hash_append(HashAlgorithm& h, X const& x) noexcept
{
using std::hash_append;
hash_append(h, x.date_);
hash_append(h, x.data_);
}
}
For more details, see the presentation by the author @HowardHinnant at CppCon14 (slides, video). Full source code by both the author and Bloomberg is available.
Upvotes: 4
Reputation: 7953
Do not specialise std::hash
, instead write your own hash function object (see Edge_Hash
below) and declare your unordered_set
with two template arguments.
#include <unordered_set>
#include <functional>
namespace foo
{
// an edge is a link between two nodes
struct Edge
{
size_t src, dst;
};
// this is an example of symmetric hash (suitable for undirected graphs)
struct Edge_Hash
{
inline size_t operator() ( const Edge& e ) const
{
static std::hash<size_t> H;
return H(e.src) ^ H(e.dst);
}
};
// this keeps all edges in a set based on their hash value
struct Edge_Set
{
// I think this is what you're trying to do?
std::unordered_set<Edge,Edge_Hash> edges;
};
}
int main()
{
foo::Edge_Set e;
}
Related posts are, eg:
Upvotes: 1
Reputation: 201
Thanks to everyone.
I have found the reason and solved the problem somehow: visual studio accepted the InstanceHash
when I was defining instances_
. Since I was changing the use of set
to unordered_set
, I forgot to specify InstanceHash
when I tried to get the const_iterator
, so this time the compiler tried to use the std::hash<>
things and failed. But the compiler didn't locate the line using const_iterator
, so I mistakenly thought it didn't accept InstanceHash
when I was defining instances_
.
I also tried to specialize the std::hash<>
for class Instance. However, this specialization requires at least the declaration of class ca::Instance
and some of its member functions to calculate the hash value. After this specialization, the definition of class ca::InstanceManage
will use it.
I now generally put declarations and implementations of almost every classes and member functions together. So, the thing I need to do is probably to split the ca
namespace scope to 2 parts and put the std{ template <> struct hash<ca::Instance>{...}}
in the middle.
Upvotes: 0