4

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
Jonathan H
  • 6,750
  • 5
  • 39
  • 68
John Smith
  • 201
  • 1
  • 12

3 Answers3

4

There are several ways.

Specializing 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.

Passing your hash to 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.

Using 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.

Community
  • 1
  • 1
TemplateRex
  • 65,583
  • 16
  • 147
  • 283
  • 1
    I would upvote +10 because this is awesome, but it doesn't really answer the OP does it? – Jonathan H Sep 22 '15 at 08:49
  • @Sh3ljohn there is working code that implements this and avoids the use of `std::hash` entirely. In your answer, the statement `H(e.src) ^ H(e.dst)` is the weak link. It could destroy all the nice properties of your hash function. – TemplateRex Sep 22 '15 at 09:01
  • @TemplateRex Actually, I don't know what does "Standard proposal" means. Does it means the proposal will be C++ standard some day? How about the compiler support? Will the VC++ support it? – John Smith Sep 22 '15 at 12:07
  • @TemplateRex I tried `using std::hash_append;` in visual studio 2013 and pressed `F1` key for `hash_append`. The online document system didn't find information about it. So I think this feature is not supported by visual studio. What c++ library do you find this feature in? – John Smith Sep 22 '15 at 12:16
  • @JohnSmith just download hash_append.h from hinnant's github (see link in answer). You might want to upgrade to VC++ 2015 because of the c++14 requirements. This library could become part of the C++ Standard, but so far it is only proposed, not decided – TemplateRex Sep 22 '15 at 12:24
  • @TemplateRex Why would you say the xor is the weak link? I appreciate that your solution is elegant and less verbose, but it is not essentially different from hashing the contents of an instance manually in a separate function object, it is not standard as of now, it is certainly more complex and definitely not compatible with either c++11 or VS2013 which the OP is using.. I still like your post, but it is both out of scope and incompatible with the apparent programming skills of the OP. – Jonathan H Sep 22 '15 at 14:36
  • @Sh3ljohn I'd recommend that you read N3980. It solves the problem of combining hashes of different sub-objects. Even if you have a perfect and great hash function (with proven distributional properties), simply doing `h(x) ^ h(y)` will destroy these nice properties. Instead, you want to be able to concanate the byte streams of the subjects into the (likely stateful) hash algorithm. BTW, SO answers are not just aimed at the OP but at the public at large, which is C++ programmers. And using hash_append.h from GitHub does not require that much skill. – TemplateRex Sep 22 '15 at 14:42
1

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:

Community
  • 1
  • 1
Jonathan H
  • 6,750
  • 5
  • 39
  • 68
  • I am developing a C++ library code. So, there is no main() in the instance_management.h file. And I have to try to put everything in namespace, including the class Instance and InstanceManager which uses unordered_set. However, it's said on many pages that it's encouraged to put specialized std::hash<> for user defined type in std namespace. I wrote the code snippet and tried to put it some where in the same file but couldn't successfully compile the instance_management.h file. – John Smith Sep 21 '15 at 15:09
  • When I put your code in a separate .h file, visual studio compiled it successfully. However, when I did the same way for my code and use ` private: unordered_set instances_;`, I get errors like: – John Smith Sep 22 '15 at 00:44
  • errors like: `error C2338: The C++ Standard doesn't provide a hash for this type.`and `see reference to class template instantiation 'std::hash<_kty>' being compiled 1> with 1> [ 1> _Kty=ca::Instance 1> ]`. It seems the compiler didn't accept the InstanceHash specified by me. – John Smith Sep 22 '15 at 00:54
0

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.

John Smith
  • 201
  • 1
  • 12
  • @Sh3ljohn The proposal to put template specialization to the std namespace comes from here http://stackoverflow.com/questions/8157937/how-to-specialize-stdhashkeyoperator-for-user-defined-type-in-unordered Also, if I don't put the specialization to the std namespace, I would need to specify the `InstanceHash` every time I tried to get a iterator as like: – John Smith Sep 22 '15 at 11:53
  • as like:`unordered_set::const_iterator iterator = instances_.find(...)` I wonder which is the better programming practice: put the template specialization in std or not. – John Smith Sep 22 '15 at 11:59
  • This is why classes usually start by defining a bunch of types using `typedef`. The better programming practice is definitely NOT to put it in the std namespace. – Jonathan H Sep 22 '15 at 14:23