Ben L
Ben L

Reputation: 7068

How to use stdext::hash_map where the key is a custom object?

Using the STL C++ hash_map...

class MyKeyObject
{
    std::string str1;
    std::string str2;

    bool operator==(...) { this.str1 == that.str1 ... }
};

class MyData
{
    std::string data1;
    int data2;
    std::string etcetc;
};

like this...

MyKeyObject a = MyKeyObject(...);
MyData b = MyData(...);

stdext::hash_map <MyKeyObject, MyData> _myDataHashMap;
_myDataHashMap[ a ] = b;

I get a whole load of errors. Here are the first three...

Error 1 error C2784: 'bool std::operator <(const std::_Tree<_Traits> &,const std::_Tree<_Traits> &)' : could not deduce template argument for 'const std::_Tree<_Traits> &' from 'const MyKeyObject' c:\program files\microsoft visual studio 8\vc\include\functional 143

Error 2 error C2784: 'bool std::operator <(const std::basic_string<_Elem,_Traits,_Alloc> &,const _Elem *)' : could not deduce template argument for 'const std::basic_string<_Elem,_Traits,_Alloc> &' from 'const Tasking::MyKeyObject' c:\program files\microsoft visual studio 8\vc\include\functional 143

Error 3 error C2784: 'bool std::operator <(const _Elem *,const std::basic_string<_Elem,_Traits,_Alloc> &)' : could not deduce template argument for 'const _Elem *' from 'const MyDataObject' c:\program files\microsoft visual studio 8\vc\include\functional 143

...

If I set the key to something simple like an int all is well.

What am I doing wrong?! Maybe I need to do something with templates?

Is there a better (quicker?) way of accessing data using a custom key object like this?

Upvotes: 6

Views: 16506

Answers (4)

Ant6n
Ant6n

Reputation: 2007

I've come across this very old question while trying to find the same answer, and find the existing answers not really helpful. Nowadays we use unordered_map if we want a hash map, and the best way to make your MyKeyObject class usable as a key in a hash_map in general is to define a hash function for the class, and tell the standard library to use this hash function for maps. This means we can instantiate the map template without always providing a hash function.

The wikipedia page on 'Unordered Associative Containers in C++' provides an easy to follow example, I dumbed it down a bit and applied it to your case. First we'll define a simple hash function as a member method:

#include <functional>
class MyKeyObject {
private:
    std::string str1;
    std::string str2;

public:
    inline size_t hash() const {
        return std::hash<std::string>()(str1) ^ std::hash<std::string>()(str2);
    }

    inline bool operator==(const MyKeyObject& other) const {
        return str1 == other.str1 && str2 == other.str2;
    }
};

In order to make the hash function, we xor the hashes of all contained objects together. This is done using std::hash, a template which has to be instantiated with the child type. Note that we cannot use this as the third template parameter to an unordered_map. Note also the const-equals operator.

Now we have to tell the standard library that this is the hash function to be used for MyKeyObject values:

namespace std {
    template <>
    class hash<MyKeyObject> {
    public:
        size_t operator()(const MyKeyObject &aMyKeyObject) const {
            return aMyKeyObject.hash();
        }
    };
}

This adds a template specialization to the std::hash template class, providing the hash operator for the MyKeyObject class. The example no the wikipedia page directly defines the hash here, rather than calling to a hash-function which is a member of the object - but if the hash function has to access private members, that won't work.

Now you should be able to use MyKeyObject in an unordered_map like so:

  std::unordered_map<MyKeyObject, MyData> _myDataHashMap;

(tested with clang/xcode)

Upvotes: 0

wh1sp3r
wh1sp3r

Reputation: 1692

I am using it for mapping struct of vertex data.

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <boost/unordered_map.hpp>



struct VERTEX
{
float x,y,z;
};

typedef boost::unordered_map<std::string, unsigned int> map;

int main()
{
VERTEX v1,v2,v3;

v1.x = 5.0; v1.y = 2.0; v1.z = 2.33333336;
v2.x = 5.0; v2.y = 2.0; v2.z = 2.32333336;
v3.x = 5.0; v3.y = 2.0; v3.z = 2.33333336;

unsigned int vertexSize = sizeof( VERTEX );
char * v1c = new char[vertexSize];
char * v2c = new char[vertexSize];
char * v3c = new char[vertexSize];

memcpy( v1c, &v1, vertexSize );memcpy( v2c, &v2, vertexSize );memcpy( v3c, &v3, vertexSize );
map mymap;

std::string aaa( v1c, vertexSize );
std::string bbb( v2c, vertexSize );
std::string ccc( v3c, vertexSize );

mymap[ aaa ] = 1;
mymap[ bbb ] = 2;

unsigned int a = mymap[ aaa ];
unsigned int b = mymap[ bbb ];
unsigned int c = mymap[ ccc ];


return 0;
}

This is just a small example, how i am using custom types. I just copy part of memory of struct into char* and then i create string with second param, which is size, size is important as memory data can contain null characters. I don't need any additional comparing, hashing functions...

Upvotes: 0

Marius
Marius

Reputation: 3501

Try the following, worked for me in VS 2005. This is a solution for both VS2005 built-in hash_map type in stdext namespace as well as the boost unordered_map (preferred). Delete whichever you don't use.

#include <boost/unordered_map.hpp>
#include <hash_map>

class HashKey
{
public:
    HashKey(const std::string& key)
    {
        _key=key;
    }
    HashKey(const char* key)
    {
        _key=key;
    }

    // for boost and stdext
    size_t hash() const
    {
        // your own hash function here
        size_t h = 0;
        std::string::const_iterator p, p_end;
        for(p = _key.begin(), p_end = _key.end(); p != p_end; ++p)
        {
            h = 31 * h + (*p);
        }
        return h;
    }
    // for boost
    bool operator==(const HashKey& other) const
    {
        return _key == other._key;
    }

    std::string _key;
};

// for boost
namespace boost
{
    template<>
    class hash<HashKey>
    {
    public :
        std::size_t operator()(const HashKey &mc) const
        {
            return mc.hash();
        }
    };
}

// for stdext
namespace stdext
{
    template<>
    class hash_compare<HashKey>
    {
    public :
        static const size_t bucket_size = 4;
        static const size_t min_buckets = 8;

        size_t operator()(const HashKey &mc) const
        {
            return mc.hash();
        }

        bool operator()(const HashKey &mc1, const HashKey &mc2) const
        {
            return (mc1._key < mc2._key);
        }
    };
}

int _tmain(int argc, _TCHAR* argv[])
{
    {
        stdext::hash_map<HashKey, int> test;
        test["one"] = 1;
        test["two"] = 2;
    }

    {
        boost::unordered_map<HashKey, int> test(8); // optional default initial bucket count 8
        test["one"] = 1;
        test["two"] = 2;
    }

    return 0;
}

Upvotes: 2

newacct
newacct

Reputation: 122429

To use a hash table, you need to specify a hash function. You need to create a function object which represents a function that takes a MyKeyObject object and returns a size_t. Then you pass the functor as the second argument after the initial size:

hash_map <MyKeyObject, MyData> _myDataHashMap(initial_size, YourHashFunctor());

Alternately, you can write your hash function as the template specialization of the hash<T> functor for your type; that way you don't need to pass in a custom hash function.

I don't know why you are getting those errors specifically. Perhaps it's trying to use the your object as the hash code or something? In any case it should not work without a hash function. Hash functions are pre-defined for the integer types and strings.

Upvotes: 3

Related Questions