Question

I'm converting a C++ program over from Windows building with Visual C++ 2008 to build on Linux with gcc 4.6.1. There's a module that uses <unordered_map>. In VC++, it seems to be perfectly ok to

#include <unordered_map>

...



std::tr1::unordered_map<mystruct, int> my_map;

We're actually supporting more compilers than just gcc 4.6 and VC++ 2008, so using pure C++2011 code isn't feasible. gcc gets upset with #include <unordered_map>, complaining that this is the true-blue c++2011 include file, so one of the things I had to do to make this work was change the include to

#include <tr1/unordered_map>

...


std::tr1::unordered_map<mystruct, int> my_map;

This works. Fair enough. Now I've got another problem, though. Here's the definition for mystruct:

struct mystruct
{
#ifdef __cplusplus
    inline operator size_t() const
    {
        return m_val;
    }
#endif
    unsigned int m_val;
};

In VC++ 2008, this seems to be as much as std::hash needs to specialize on mystruct. std::tr1::hash, on the other hand, doesn't like this, at least not on gcc 4.6.1. It refuses to link, complaining that std::tr1::hash<mystruct>::operator()( mystruct ) const is undefined. I'm not sure whether that happens with VC++ when I do the proper tr1 include - maybe it'll complain about the same thing? I'll try it tomorrow, but for now all I've got is a linux box with gcc on it. For now, I had to do this to get it working:

namespace std {
    namespace tr1 {
        std::size_t hash<mystruct>::operator()( mystruct & c ) const 
        { 
            return c.m_val; 
        }
    }
}

Can anyone enlighten me on how this is supposed to work? It seems so much more elegant to be able to define a size_t operator on a type that you want to be hashable, but I'm willing to live with defining the operator() on std::tr1::hash.

UPDATE:

I tried specializing the entire hash class, as suggested. Building with gcc, I get

myfile.cpp:41:12: error: specialization of 'std::tr1::hash<mystruct>' after instantiation
myfile.cpp:41:12: error: redefinition of 'struct std::tr1::hash<mystruct>'
/usr/include/c++/4.6/tr1/functional_hash.h:45:12: error: previous definition of 'struct std::tr1::hash<mystruct>'
Was it helpful?

Solution

Microsoft's way, to accept an implicit conversion to std::size_t, is an extension.

GCC's way, to specialize std::tr1::hash, is the way actually defined by TR1 which is now standardized in C++11 (of course with the tr1:: part dropped).

MSVC should still accept the hash specialization, and of course both should accept a completely new class passed as the Hasher template argument.

It is better style to specialize the entire class, not just the operator() function:

namespace std {
    namespace tr1 {
        template<>
        struct hash< mystruct > {
            std::size_t operator()( mystruct & c ) const 
            { 
                return c.m_val; 
            }
        };
    }
}

OTHER TIPS

Since your mystruct is defined by user, you need to provide a hash function for unordered_map:

struct my_hash {
std::size_t operator()( mystruct & c ) const 
        { 
            return c.m_val; 
        }
};

std::tr1::unordered_map<mystruct, int, my_hash> my_map;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top