1

I have to make an unordered_map consisting of the following keys and value:

  • key: unsigned char, unsigned char, unsigned char

  • value: structure(int,int)

Here is my code to define that:

namespace G {

typedef std::tuple< unsigned char, unsigned char , unsigned char> key_t;


struct IndexGuide
{
int index;
int SerialNo;

};

typedef std::unordered_map<const key_t,IndexGuide> GuideDouble;

}
using namespace G;

Is there anything wrong in this code because when I link these files on linux terminal. It gives me errors.Some part of the error as follows

In function `std::__detail::_Hash_code_base const, std::pair const, G::IndexGuide>, std::_Select1st const, G::IndexGuide> >, std::equal_to const>, std::hash const>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, false>::_M_bucket_index(std::__detail::_Hash_node const, G::IndexGuide>, false> const*, unsigned int) const':

4pie0
  • 29,204
  • 9
  • 82
  • 118
Xara
  • 8,748
  • 16
  • 52
  • 82

4 Answers4

2

You have to provide a hash function which tells std::unordered_map how to produce a hash value from your key_t. You can do it by specialization of hash template functor:

provide a Key:

struct Key{
    unsigned char  first;
    unsigned char second;
    unsigned char  third;

    bool operator==(const Key &other) const {
         return (first == other.first
            && second == other.second
            && third == other.third);
    }
};

specialize hash template:

namespace std {

  template <>
  struct hash< Key>
  {
    std::size_t operator()(const Key& k) const
    {
      using std::hash;

      // Compute individual hash values for first,
      // second and third and combine them using XOR
      // and bit shifting:

      return ((hash<char>()( k.first)
               ^ (hash<char>()( k.second) << 1)) >> 1)
               ^ (hash<int>()( k.third) << 1);
    }
  };

}

std::unordered_map< Key, IndexGuide> myMap;

As I said in comments, you can take a look at this SO thread.

Community
  • 1
  • 1
4pie0
  • 29,204
  • 9
  • 82
  • 118
  • Shouldn't that be `struct hash` instead of `struct hash`? – anderas Mar 04 '14 at 16:05
  • yes, of course, fixed – 4pie0 Mar 04 '14 at 16:06
  • I personally would think that avoiding undefined behavior is an asset. You may not specialize types in `std` when the specialization does not involve any user-owned types. `key_t` is a `std::tuple` (a `template` from `std`) and the arguments to the `template` are all basic types. Rather, you should write your own hasher, and pass it manually to the `unordered_map`. And while this is somewhat academic, I could easily see the above breaking horribly if they added `hash` specializations to `tuple`. – Yakk - Adam Nevraumont Mar 04 '14 at 16:08
  • Your hash combination is only mediocre as well (not horrible, just mediocre) -- I'd duplicate `boost`s technique myself. – Yakk - Adam Nevraumont Mar 04 '14 at 16:10
  • this is not undefined behavior to specialize hash template this way. According to the body of hash function: this is just merely example, you are free to do whatever you want in there. I agree it would be better to create class Key and specialized template on it – 4pie0 Mar 04 '14 at 16:13
  • @piotruś That is only because you changed the answer after I made a comment. – Yakk - Adam Nevraumont Mar 04 '14 at 16:38
  • no, you have to refer to the changed version because you didn't respond on time. Yes, I was talking about version with tuple, if you think this was UB please show appropriate quote from the standard – 4pie0 Mar 04 '14 at 16:42
  • **17.6.4.2.1 Namespace std** [namespace.std]/ 1 ""The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std unless otherwise specified. A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited."" – Yakk - Adam Nevraumont Mar 04 '14 at 16:45
2

So your basic problem is that you don't have a hasher for std::tuple -- the standard library does not currently come with one by default. I think this is an oversight.

You cannot add a default hasher for std::tuple -- either for all tuples or for your particular list -- because the standard says you aren't allowed to:

17.6.4.2.1 Namespace std [namespace.std]/1 The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std unless otherwise specified. A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.

So this leaves you with either writing a custom hasher for your tuple type, and passing it to your unordered_map, or using a type that isn't merely std::tuple for your key, or writing a custom hasher that supports every tuple (including recursive tuples) while also supporting hash<T> lookup for non-tuples. A 4th option would be to write your own ADL-based hash system and set it up so that types with valid std::hash specializations use that, with various other fallbacks.

I will illustrate the 3rd option above.

First, you want to be able to combine two hash values in a way that doesn't lead to many spurious collisions.

  inline std::size_t hash_result_combine(std::size_t lhs, std::size_t rhs)
  {
      return lhs ^( rhs + 0x9e3779b9 + (lhs<<6) + (lhs>>2));
  }

this takes two hash outputs, and combines them. We can then chain this any number of times. (constants borrowed from https://stackoverflow.com/a/7111460/1774667 )

Next, some indexing boilerplate. Most generic work with std::tuple requires this stuff:

  template<unsigned...>struct indexes{};
  template<unsigned Max, unsigned... Is>struct make_indexes:make_indexes<Max-1,Max-1,Is...> {};
  template<unsigned...Is>struct make_indexes<0,Is...>:indexes<Is...>{};

Finally, a hasher class that works with nearly every type:

  struct my_hasher {
    template<typename... Args>
    std::size_t operator()(indexes<>, std::tuple<Args...> const& tup) const {
      return 0;
    }
    template<unsigned I, unsigned... Is,typename... Args>
    std::size_t operator()(indexes<I, Is...>,std::tuple<Args...> const& tup) const {
      return hash_result_combine( (*this)(std::get<I>(tup)), (*this)(indexes<Is...>, tup) );
    }
    template<typename... Args>
    std::size_t operator()(std::tuple<Args...> const& tup) const {
      return (*this)(make_indexes<sizeof...(Args)>(), tup);
    }
    template<typename T>
    std::size_t operator()(T const& t) const { return std::hash<T>()(t); }
  };

you can pass my_hasher to an unordered_set in place of hash<T> for tuples, tuples containing tuples, or even for scalars -- it is very generous.

typedef std::unordered_map<const key_t,IndexGuide, my_hasher> GuideDouble;

and now we properly hash key_t.

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

unordered_set is implemented using a hash table.
in order to use unordered_set for a type that you defined you need to specialize the hash<> template for that type, as follows:

namespace std {
    template <> struct hash<key_t>
    {

        size_t operator()(const key_t & t) const
        {
            // the logic for hashing
        }
    };
}
Moha the almighty camel
  • 4,327
  • 4
  • 30
  • 53
0

There is no default hasher for std::tuple.

You may use this code snippet Generic hash for tuples in unordered_map / unordered_set

Community
  • 1
  • 1
Marat Buharov
  • 331
  • 5
  • 18