C++:将自定义哈希键值从 unordered_map 输出到 std::cout 时出错

C++: Error outputting a custom hash key value from unordered_map to std::cout

我正在尝试构建一个 std::unordered_map 并将自定义类型作为键。自定义类型是一个简单的 std::vector<double>。这个想法是它将作为网格上 2D 点的方便容器。除了输出散列密钥外,一切正常。这是我放在一起来说明这个想法的示例:

#include <iostream>
#include <vector>
#include <unordered_map>
#include <boost/functional/hash.hpp>
#include <chrono>

namespace std
{
    template<typename Container>
    struct hash {
            std::size_t operator()(Container const& v) const
            {
                return boost::hash_range(v.begin(), v.end());
            }
    };
}

int main()
{

    std::unordered_map<std::vector<double>, double> test;

    unsigned long t = (unsigned long) std::chrono::system_clock::now().time_since_epoch().count();
    std::srand(t);
    for (uint i = 0; i < 100 ; ++i)
    {
        double d1 = i/200.0;
        double d2 = i/200.0;
        std::vector<double> v({d1, d2});
        test[v] = d1;
    }

    std::cout << "Size:" << test.size() << std::endl;
    for (const auto& it : test )
    {
        std::cout << it.first << ":" << it.second << std::endl;
    }

    return 0;
}

散列专业化模板由另一个 SO 线程提供。问题是当我尝试编译上面的代码时 g++ 吐出以下错误:

cannot bind 'std::ostream {aka std::basic_ostream<char>}' lvalue to 'std::basic_ostream<char>&&'
   std::cout << it.first << ":" << it.second << std::endl;
                   ^

很明显是绊倒了it.first。如果我删除 it.first,代码将正确编译和运行。我知道输出将 而不是 是双精度向量。我确实环顾四周 SO 了很长一段时间,但我找不到关于如何 std::cout 来自具有自定义键类型的无序映射的哈希值的明确答案。任何反馈将不胜感激。

提前致谢!

编辑:

谢谢大家的意见。这是我第一次遇到非原始类型作为散列键,所以我对 key/value 对的存储方式有错误的想法(我假设散列值是键,而实际上它是实际的自定义类型)。

据我所知,没有用于从 std::unordered_map.

公开散列值(与散列函数相反)的标准接口

如您所见,取消引用 std::unordered_map<Key,V>::iterator 会产生可转换为 std::unordered_map<Key,V>::value_type 的内容,而 std::unordered_map<Key,V>::value_type 又是表示 (key,value) 对的 std::pair<const Key,V>,而不是 (散列键,值)对。

相应地,it.first 给你 std::vector<double> 而不是 std::size_t

unordered_map<K,V>value_typepair<const K, V>。这就是您使用范围遍历它时得到的结果。 vector 没有 operator<< 过载,导致您看到的错误。


namespace std
{
    template<typename Container>
    struct hash {
            std::size_t operator()(Container const& v) const
            {
                return boost::hash_range(v.begin(), v.end());
            }
    };
}

这不是 std::hash 的专业化。这是对主模板的重新定义,在您的情况下,主模板仅由纯偶然事件编译。 (实现必须保留未定义的主要 std::hash 模板,并且必须在 std 命名空间而不是内联命名空间中实际声明 hash。您的代码 breaks up completely on libc++,例如。)

专业化看起来像

namespace std
{
    // full specialization
    template<>
    struct hash<Foo> {
    //         ^^^^^
            std::size_t operator()(Foo const& v) const
            {
                // ...
            }
    };

    // partial specialization
    template<typename T>
    struct hash<Bar<T>>{
    //         ^^^^^^^^
            std::size_t operator()(Bar<T> const& v) const
            {
                // ...
            }
    };
}

请注意 hash 之后的显式模板参数列表。这表明这是一个专业化。

无论如何,将 std::hash 专门化为 std::vector<double> 是非法的,因为它不依赖于用户定义的类型。编写自己的哈希器很容易:

struct container_hasher {
    template<typename Container>
    std::size_t operator()(Container const& v) const
    {
        using std::begin; 
        using std::end;
        return boost::hash_range(begin(v), end(v));
    }
};

请注意,我模板化了 operator() 而不是类型本身 - 这使得编写哈希类型更容易。 using 后跟一个不合格的调用为 beginend.

启用 ADL

然后test的定义就变成了

std::unordered_map<std::vector<double>, double, container_hasher> test;