1

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>'
Ted Middleton
  • 6,859
  • 10
  • 51
  • 71

2 Answers2

6

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; 
            }
        };
    }
}
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • Exactly what I wanted to know. Thank you! – Ted Middleton May 07 '12 at 06:15
  • Interestingly enough, I did try specializing the entire class, but gcc didn't like that. See above. – Ted Middleton May 07 '12 at 06:20
  • 1
    @TedMiddleton That looks like you need to move the specialization to be defined earlier, before `hash< mystruct >` is used (or `unordered_map` uses it). Specializing a function after the class is instantiated but before the function itself is used is undefined behavior, (or is it ill formed, no diagnostic required? I forget), so although it appears to be a fix your function specialization is really a bit unpredictable. This is why specializing the class is better style. – Potatoswatter May 07 '12 at 06:36
  • Yes - it looks like that was it. I moved the specialization closer to the definition of `mystruct`, and the error went away. It turns out that another header was already defining an `unordered_map`. Now, unfortunately, this still doesn't work. Something that I didn't mention is that `mystruct` is actually defined with C linkage - hence the `__cplusplus` guard. It looks like gcc doesn't want to let me specialize `std::tr1::hash` on a type with C linkage. So I've just decided to use my own hash functor, much like @user2k5 suggested. – Ted Middleton May 07 '12 at 18:06
  • . No - it looks like I was wrong again. Someone was mistakenly including the `mystruct.h` file within `extern "C" {}`. I thought that it was a bit strange that gcc didn't like specializing on a plain old struct with c linkage. The error was actually much simpler, if much harder to find. Specializing the whole of `std::tr1::hash` works a-ok now. Thanks! – Ted Middleton May 07 '12 at 19:59
  • On OSX this solution *almost* worked - for me I needed to add const to argument - it becomes `const mystruct &c` – Ash Berlin-Taylor Nov 08 '13 at 14:40
2

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;
user2k5
  • 827
  • 1
  • 7
  • 9