Неупорядоченная карта с тремя беззнаковыми символами в качестве ключа

Я должен сделать unordered_map, состоящий из следующих ключей и значений:

  • ключ: беззнаковый символ, беззнаковый символ, беззнаковый символ

  • значение: структура (int, int)

Вот мой код, чтобы определить это:

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;

Что-то не так в этом коде, потому что, когда я связываю эти файлы на терминале Linux. Это дает мне ошибки. Некоторая часть ошибки выглядит следующим образом

В функции `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':


person Xara    schedule 04.03.2014    source источник
comment
вы должны предоставить хэш-функцию, которая сообщает unordered_map, как создать хэш-значение из вашего key_t   -  person 4pie0    schedule 04.03.2014
comment
Пожалуйста, поделитесь с нами полной ошибкой. В той части, что вы выложили, только написано, что есть ошибка, но не какая именно.   -  person anderas    schedule 04.03.2014
comment
Удалите const из const key_t в typedef; это не должно быть там.   -  person Angew is no longer proud of SO    schedule 04.03.2014


Ответы (4)


Таким образом, ваша основная проблема заключается в том, что у вас нет хэшера для std::tuple -- стандартная библиотека в настоящее время не поставляется с ним по умолчанию. Я думаю, что это упущение.

Вы не можете добавить хэш по умолчанию для std::tuple — ни для всех tuple, ни для вашего конкретного списка — потому что стандарт говорит, что вам не разрешено:

17.6.4.2.1 Пространство имен std [namespace.std]/1 Поведение программы C++ не определено, если она добавляет объявления или определения в пространство имен std или в пространство имен внутри пространства имен std, если не указано иное. Программа может добавить специализацию шаблона для любого шаблона стандартной библиотеки в пространство имен std только в том случае, если объявление зависит от определяемого пользователем типа и специализация соответствует требованиям стандартной библиотеки для исходного шаблона и не запрещена явно.

Таким образом, вам остается либо написать собственный хэш для вашего типа tuple и передать его вашему unordered_map, либо использовать тип, который не является просто std::tuple для вашего ключа, либо написать собственный хэш, который поддерживает каждый tuple (включая рекурсивные tuples) а также поддерживает поиск hash<T> для не-tuples. Четвертый вариант — написать свою собственную хэш-систему на основе ADL и настроить ее так, чтобы типы с допустимыми специализациями std::hash использовали ее с различными другими резервными вариантами.

Я проиллюстрирую 3-й вариант выше.

Во-первых, вы хотите иметь возможность комбинировать два хэш-значения таким образом, чтобы это не приводило к множеству ложных коллизий.

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

это берет два выхода hash и объединяет их. Затем мы можем связать это любое количество раз. (константы заимствованы из https://stackoverflow.com/a/7111460/1774667)

Далее немного шаблонного индексирования. Для большинства общих работ с std::tuple требуются следующие вещи:

  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...>{};

Наконец, класс хеширования, который работает почти со всеми типами:

  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); }
  };

вы можете передать my_hasher в unordered_set вместо hash<T> для tuples, tuples, содержащих кортежи, или даже для скаляров - это очень щедро.

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

и теперь мы правильно хэшируем key_t.

person Yakk - Adam Nevraumont    schedule 04.03.2014

Вы должны предоставить хэш-функцию, которая сообщает std::unordered_map, как создать хеш-значение из вашего key_t. Вы можете сделать это с помощью специализации функтора шаблона hash:

предоставить ключ:

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);
    }
};

специализированный хеш-шаблон:

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;

Как я уже сказал в комментариях, вы можете взглянуть на эту ветку SO.

person 4pie0    schedule 04.03.2014
comment
Разве это не должно быть struct hash<key_t> вместо struct hash<Key>? - person anderas; 04.03.2014
comment
да конечно исправили - person 4pie0; 04.03.2014
comment
Лично я считаю, что избегание неопределенного поведения является преимуществом. Вы не можете специализировать типы в std, если специализация не включает какие-либо пользовательские типы. key_t — это std::tuple (template из std), а аргументы template — все базовые типы. Скорее, вы должны написать свой собственный хэш и передать его вручную в unordered_map. И хотя это несколько академично, я мог бы легко увидеть, как вышеприведенное ужасно сломается, если они добавят hash специализаций к tuple. - person Yakk - Adam Nevraumont; 04.03.2014
comment
Ваша хеш-комбинация тоже посредственная (не ужасная, просто посредственная) - я бы сам воспроизвел технику boost. - person Yakk - Adam Nevraumont; 04.03.2014
comment
это не неопределенное поведение для такой специализации хеш-шаблона. Согласно телу хэш-функции: это всего лишь пример, вы можете делать там все, что хотите. Согласен, лучше было бы создать класс Key и специализированный шаблон на нем - person 4pie0; 04.03.2014
comment
@piotruś Это только потому, что вы изменили ответ после того, как я оставил комментарий. - person Yakk - Adam Nevraumont; 04.03.2014
comment
нет, вы должны сослаться на измененную версию, потому что вы не ответили вовремя. Да, я говорил о версии с кортежем, если вы думаете, что это UB, покажите соответствующую цитату из стандарта. - person 4pie0; 04.03.2014
comment
17.6.4.2.1 Пространство имен std [namespace.std]/ 1 Поведение программы на C++ не определено, если она добавляет объявления или определения в пространство имен std или в пространство имен внутри пространства имен std, если не указано иное. Программа может добавить специализацию шаблона для любого шаблона стандартной библиотеки в пространство имен std только в том случае, если объявление зависит от определяемого пользователем типа и специализация соответствует требованиям стандартной библиотеки для исходного шаблона и не запрещена явно. - person Yakk - Adam Nevraumont; 04.03.2014
comment
большое спасибо, теперь согласен - person 4pie0; 04.03.2014

unordered_set реализуется с помощью хеш-таблицы.
чтобы использовать unordered_set для определенного вами типа, вам необходимо специализировать шаблон hash<> для этого типа следующим образом:

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

        size_t operator()(const key_t & t) const
        {
            // the logic for hashing
        }
    };
}
person Moha the almighty camel    schedule 04.03.2014
comment
это вызовет неопределенное поведение, класс key_t должен быть пользовательским классом - person 4pie0; 04.03.2014

Для std::tuple нет хэшера по умолчанию.

Вы можете использовать этот фрагмент кода Общий хэш для кортежей в unordered_map/unordered_set

person Marat Buharov    schedule 04.03.2014