应该std :: hash <t>当t是std :: pair <two simpler =“”types =“”还=“”supported =“”by =“”std :: hash =“”>?</two > </T>

时间:2015-01-14 18:43:55

标签: c++ c++11 hash stl stdhash

我正在使用一个声明如此的有序集:

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

在对我使用该集合的方式进行一些分析之后,我得出结论,unordered_set将是一个更明智的选择。但当我将std :: set更改为std :: unordered_set时,我从编译器(g ++ 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标准的相关信息,可以解释为什么出现问题。 (对于g ++为此发出的错误文本的难以理解的墙,没有很好的解释。)

在我看来,

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知道如何为一个对象构造一个哈希值,该对象是一对可以清空的类型?

1 个答案:

答案 0 :(得分: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及其组件,{{1} 1}}是一个通用的哈希,试图散列传递给它的任何东西。

代码可能无法编译,如图所示。

我选择将所有容器和元组散列为散列长度,然后散列其内容的组合。作为副作用,hashers::hash<>哈希与array<int, 3>相同,tuple<int,int,int>哈希与tuple<int,int>相同,而pair<int,int>哈希与{{1}相同我认为这是一个不错的财产。空数组/元组/向量/等哈希像std::vector<char>{'a','b','c', '\0'}

您可以通过简单地覆盖相关类型的命名空间中的"abc"来扩展上述系统,或者专门设置size_t(0)custom_hash来执行自定义哈希(我会选择std::hash<X>以获得最不惊讶的原则)。要获得高级用途,您可以使用SFINAE对hashers::hash<X>进行专门化处理,但我要改为std::hash代替它。