2015-01-14 2 views
2

я использовал упорядоченный набор объявлен как так:Должен ли std :: hash <T> работать, когда t является std :: pair <два более простых типа, также поддерживаемых std :: hash>?

std::set<std::pair<const std::string, const myClass *> > myset; 

После небольшого анализа, как я использовал набор, я пришел к выводу, что unordered_set будет умнее выбором. Но когда я изменил зЬй :: установлен на STD :: unordered_set, я получил огромное извергают сообщений об ошибках от моего компилятора (г ++ 4.8.1) жалуясь

invalid use of incomplete type struct std::hash<std::pair<const std::basic_string<char>, const myClass * > > 

После некоторой головной царапать, я понял, что std :: hash не знал, как обращаться с типом, который был std :: pair, несмотря на то, что два типа, которые составляли пару, были хешируемыми. Я думаю, error for hash function of pair of ints содержит соответствующую информацию о стандарте C++ 11, который объясняет, почему все пошло не так. (Там нет хорошего объяснения для непроницаемой стенки текста ошибки, что г ++ излучающего для этого.)

Казалось бы мне, что

std::hash<std::pair<T1, T2>> hasher(make_pair(x,y)) 
    = some_func(std::hash<T1>hasher(x), std::hash<T2>hasher(y)) 

где some_func() может быть столь же просто, как XOR (или нет; см. Why is XOR the default way to combine hashes?)

Есть ли веская причина, по которой стандарт не требует, чтобы std :: hash знал, как построить хеш-значение для объекта, который представляет собой пару типов, каждый из которых имеет хеширование?

+0

Можете ли вы hash 'const myClass *'? Или вы можете использовать hash 'const myClass'? –

+3

Существует [предложение] (http://isocpp.org/files/papers/n3980.html), в котором рассматривается эта проблема. Помимо этого, этот вопрос слишком широк и не отвечает на него. Пока вы можете предоставить свою собственную 'std :: hash' специализацию и использовать' boost :: hash_combine' для объединения отдельных хэшей. – Praetorian

+0

Пожалуйста, не стесняйтесь использовать программное обеспечение, стоящее за предложением, которое поддерживает Praetorian: https://github.com/HowardHinnant/hash_append Только за последние пару дней моя команда выиграла от возможности легко сравнивать хэш-алгоритм X с алгоритмом хэша Y , применительно к нашему конкретному приложению, и выберите, какой из них лучше для нас. –

ответ

0

Причина проста, она не была добавлена ​​к стандарту. То же самое относится к хэшированию других структур, таких как tuple.

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

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

namespace hashers { 
    constexpr size_t hash_combine(size_t, size_t); // steal from boost, or write your own 
    constexpr size_t hash_combine(size_t a) { return a; } 
    constexpr size_t hash_combine() { return 0; } 
    template<class...Sizes> 
    constexpr size_t hash_combine(size_t a, size_t b, Sizes... sizes) { 
    return hash_combine(hash_combine(a,b), sizes...); 
    } 

    template<class T=void> struct hash; 

    template<class A, class B> 
    constexpr size_t custom_hash(std::pair<A,B> const& p) { 
    return hash_combine(hash<size_t>{}(2), hash<std::decay_t<A>>{}(p.first), hash<std::decay_t<B>>{}(p.second)); 
    } 
    template<class...Ts, size_t...Is> 
    constexpr size_t custom_hash(std::index_sequence<Is...>, std::tuple<Ts...> const& p) { 
    return hash_combine(hash<size_t>{}(sizeof...(Ts)), hash<std::decay_t<Ts>>{}(std::get<Is>(p))...); 
    } 
    template<class...Ts> 
    constexpr size_t custom_hash(std::tuple<Ts...> const& p) { 
    return custom_hash(std::index_sequence_for<Ts...>{}, p); 
    } 
    template<class T0, class C> 
    constexpr size_t custom_hash_container(size_t n, C const& c) { 
    size_t retval = hash<size_t>{}(n); 
    for(auto&& x : c) 
     retval = hash_combine(retval, hash<T>{}(x)); 
    return retval; 
    } 
    template<class T0, class C> 
    constexpr size_t custom_hash_container(C const& c) { 
    return custom_hash_container(c.size(), c); 
    } 
    template<class T, class...Ts> 
    size_t custom_hash(std::vector<T, Ts...> const& v) { 
    return custom_hash_container<T>(v); 
    } 
    template<class T, class...Ts> 
    size_t custom_hash(std::basic_string<T, Ts...> const& v) { 
    return custom_hash_container<T>(v); 
    } 
    template<class T, size_t n> 
    constexpr size_t custom_hash(std::array<T, n> const& v) { 
    return custom_hash_container<T>(n, v); 
    } 
    template<class T, size_t n> 
    constexpr size_t custom_hash(T (const& v)[n]) { 
    return custom_hash_container<T>(n, v); 
    } 
    // etc -- list, deque, map, unordered map, whatever you want to support 
    namespace details { 
    template<class T, class=void> 
    struct hash : std::hash<T> {}; 
    using hashers::custom_hash; 
    template<class T> 
    struct hash<T,decltype(void(
     custom_hash(declval<T const&>()) 
    )) { 
     constexpr size_t operator()(T const& t)const { 
     return custom_hash(t); 
     } 
    }; 
    } 
    template<class T> 
    struct hash : details::hash<T> {}; 
    template<> 
    struct hash<void> { 
    template<class T> 
    constexpr size_t operator()(T const& t)const { return hash<T>{}(t); } 
    } 
} 

и теперь hashers::hash<T> будет рекурсивен использовать либо ADL-ищутся custom_hash функцию или std::hash, если это не удается, хэш T и ее компонентов, а также hashers::hash<> является универсальной мясорубкой, который пытается хэш ничего перешел к нему.

Код не может компилироваться, как показано на рисунке.

Я выбрал хэш всех контейнеров и кортежей как хэш их длины, а затем хеширует комбинацию их содержимого. В качестве побочного эффекта array<int, 3> хешируют то же, что и tuple<int,int,int>, и tuple<int,int> хешируют то же, что и pair<int,int>, и std::vector<char>{'a','b','c', '\0'} хешируют то же, что и "abc", что, я думаю, является прекрасным свойством. Пустые массивы/tuple/vector/etc хэши, такие как size_t(0).

Вы можете расширить вышеуказанную систему для собственных типов, просто перекрывая custom_hash в пространстве имен рассматриваемого типа, или специализирующийся либо std::hash<X> или hashers::hash<X>, чтобы сделать свой собственный хэш (я бы с std::hash по принципу наименьшего удивления себя). Для расширенного использования вы можете специализировать hashers::details::hash<X,void> с SFINAE, но я бы сказал, сделайте это для custom_hash.