【问题标题】:Should std::hash<T> work when T is std::pair<two simpler types also supported by std::hash>?当 T 是 std::pair<std::hash 也支持的两个更简单的类型> 时,std::hash<T> 是否应该工作?
【发布时间】:2015-03-13 01:34:49
【问题描述】:

我使用的是这样声明的有序集:

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

在对我使用set, 的方式进行了一些分析后,我得出结论认为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 类型,尽管构成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 知道如何为一个对象构造一个哈希值,该对象是一个 pair 类型,每个类型都是可哈希的?

【问题讨论】:

  • 你可以散列const myClass *吗?还是只能散列const myClass
  • 有一个proposal 解决了这个问题。除此之外,这个问题太宽泛,无法真正回答。目前,您可以提供自己的 std::hash 特化并使用 boost::hash_combine 组合各个散列。
  • 请随意使用 Praetorian 引用的提案背后的软件:github.com/HowardHinnant/hash_append 就在过去几天,我的团队受益于轻松比较哈希算法 X 和哈希算法 Y 的能力,适用于我们的特定应用,然后选择最适合我们的应用。

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


【解决方案1】:

原因很简单,没有加入标准。散列其他结构(如 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&lt;T&gt; 将递归地使用 ADL 查找的 custom_hash 函数或 std::hash 如果失败,对 T 及其组件进行散列,hashers::hash&lt;&gt; 是一个通用散列器,它试图散列任何传递给它的东西。

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

我选择散列所有容器和元组作为散列它们的长度,然后散列它们的内容组合。作为副作用,array&lt;int, 3&gt; 的哈希值与tuple&lt;int,int,int&gt; 相同,tuple&lt;int,int&gt; 的哈希值与pair&lt;int,int&gt; 相同,std::vector&lt;char&gt;{'a','b','c', '\0'} 的哈希值与"abc" 相同,我认为这是一个不错的属性。空数组/元组/向量/等哈希,如size_t(0)

您可以通过简单地在相关类型的命名空间中覆盖custom_hash 来为您自己的类型扩展上述系统,或者专门使用std::hash&lt;X&gt;hashers::hash&lt;X&gt; 来执行您的自定义哈希(我会选择@987654340 @为了我自己最不惊讶的原则)。对于高级用途,您可以使用 SFINAE 专门化 hashers::details::hash&lt;X,void&gt;,但我会建议使用 custom_hash 来代替。

【讨论】:

  • 更好的哈希框架,不需要哈希组合:open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3980.html
  • 警告:为无序容器编写自定义哈希应该尊重它们的重要==
  • @Caleth 我不确定该警告在哪里解决。如果两个无序容器返回== true,则您的哈希必须相同,但我没有看到任何不同意上述建议或实现。你能指出来吗?
  • 天真的hash_combine_range(unordered.begin(), unordered.end()) 不一定行得通
  • @caleth 啊,因为两个桶数不同的无序容器迭代不同。
猜你喜欢
  • 2023-02-16
  • 1970-01-01
  • 1970-01-01
  • 2015-04-06
  • 2017-11-16
  • 1970-01-01
  • 2016-07-31
  • 2011-12-16
  • 2014-08-13
相关资源
最近更新 更多