在c++11 的unordered_set和unordered_map中插入pair或tuple作为键值

本文介绍如何在C++中将pair或tuple用作unordered_set和unordered_map的键值,解决std::hash无法直接应用于这些类型的问题。通过自定义hash函数、使用boost::hash或hash_combine方法,实现复合类型的哈希,适用于C++11及以后的版本。

想完成的任务 与 遇到的问题

想在c++11 的unordered_set和unordered_map中插入pair或tuple作为键值

std::unordered_map<std::pair<std::string,std::string>, int> m;

会报错
/usr/include/c++/4.9/bits/hashtable_policy.h: In instantiation of 'struct std::__detail::__is_noexcept_hash<std::tuple<int, int>, std::hash<std::tuple<int, int> > >'
或者
/usr/include/c++/4.9/bits/hashtable_policy.h: In instantiation of 'struct std::__detail::__is_noexcept_hash<std::pair<std::basic_string<char>, std::basic_string<char> >, std::hash<std::pair<std::basic_string<char>, std::basic_string<char> > > >'
C++的std::pair是无法std::hash的,为了在unordered_setunordered_map中使用std::pair,有如下方法。还有个前提,pair 和 tuple 中的元素本身得是可以 std::hash 哈希的。

方法一:专门写个可用于std::pair的std::hash

#include <iostream>
#include <unordered_map>
#include <utility>

typedef std::pair<std::string,std::string> pair;

struct pair_hash
{
	template <class T1, class T2>
	std::size_t operator() (const std::pair<T1, T2> &pair) const
	{
		return std::hash<T1>()(pair.first) ^ std::hash<T2>()(pair.second);
	}
};

int main()
{
	std::unordered_map<pair,int,pair_hash> unordered_map =
	{
		{{"C++", "C++11"}, 2011},
		{{"C++", "C++14"}, 2014},
		{{"C++", "C++17"}, 2017},
		{{"Java", "Java 7"}, 2011},
		{{"Java", "Java 8"}, 2014},
		{{"Java", "Java 9"}, 2017}
	};

	for (auto const &entry: unordered_map)
	{
		auto key_pair = entry.first;
		std::cout << "{" << key_pair.first << "," << key_pair.second << "}, "
				  << entry.second << '\n';
	}

	return 0;
}

输出

{Java,Java 8}, 2014
{Java,Java 7}, 2011
{Java,Java 9}, 2017
{C++,C++17}, 2017
{C++,C++14}, 2014
{C++,C++11}, 2011

注意:上面的代码使用的异或(XOR),由于x^x == 0并且x^y == y^x,所以应该配合一些位运算的shift或rotate来做。

方法二:使用boost::hash

boost::hash可以用于哈希integers, floats, pointers, strings, arrays, pairs 以及其它 STL 里的东西

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

typedef std::pair<std::string,std::string> pair;

int main()
{
	std::unordered_map<pair,int,boost::hash<pair>> unordered_map =
	{
		{{"C++", "C++11"}, 2011},
		{{"C++", "C++14"}, 2014},
		{{"C++", "C++17"}, 2017},
		{{"Java", "Java 7"}, 2011},
		{{"Java", "Java 8"}, 2014},
		{{"Java", "Java 9"}, 2017}
	};

	for (auto const &entry: unordered_map)
	{
		auto key_pair = entry.first;
		std::cout << "{" << key_pair.first << "," << key_pair.second << "}, "
				  << entry.second << '\n';
	}

	return 0;
}

输出

{Java,Java 8}, 2014
{Java,Java 9}, 2017
{Java,Java 7}, 2011
{C++,C++17}, 2017
{C++,C++14}, 2014
{C++,C++11}, 2011

注意:boost的hash的位置改过,有网友说boost 1.72的hash在

#include <boost/container_hash/extensions.hpp>

原话是

By the way the functional hash has moved. I am not sure when, but in boost 1.72 it is in #include <boost/container_hash/extensions.hpp> I am not sure why the boost hash function for a tuple is not documented somewhere.

方法三:hash_combine

把下列代码放在任何你想实现本文目的代码头文件里
原话是

This works on gcc 4.5 allowing all c++0x tuples containing standard hashable types to be members of unordered_map and unordered_set without further ado. (I put the code in a header file and just include it.)
The function has to live in the std namespace so that it is picked up by argument-dependent name lookup (ADL).

#include <tuple>
namespace std{
    namespace
    {

        // Code from boost
        // Reciprocal of the golden ratio helps spread entropy
        //     and handles duplicates.
        // See Mike Seymour in magic-numbers-in-boosthash-combine:
        //     http://stackoverflow.com/questions/4948780

        template <class T>
        inline void hash_combine(std::size_t& seed, T const& v)
        {
            seed ^= std::hash<T>()(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        }

        // Recursive template code derived from Matthieu M.
        template <class Tuple, size_t Index = std::tuple_size<Tuple>::value - 1>
        struct HashValueImpl
        {
          static void apply(size_t& seed, Tuple const& tuple)
          {
            HashValueImpl<Tuple, Index-1>::apply(seed, tuple);
            hash_combine(seed, std::get<Index>(tuple));
          }
        };

        template <class Tuple>
        struct HashValueImpl<Tuple,0>
        {
          static void apply(size_t& seed, Tuple const& tuple)
          {
            hash_combine(seed, std::get<0>(tuple));
          }
        };
    }

    template <typename ... TT>
    struct hash<std::tuple<TT...>> 
    {
        size_t
        operator()(std::tuple<TT...> const& tt) const
        {                                              
            size_t seed = 0;                             
            HashValueImpl<std::tuple<TT...> >::apply(seed, tt);    
            return seed;                                 
        }                                              

    };
}

参考资料

方法一、方法二:
《How to use std::pair as key to std::unordered_map in C++》
链接:https://www.techiedelight.com/use-std-pair-key-std-unordered_map-cpp/
方法三:
《Generic hash for tuples in unordered_map / unordered_set》问题下Leo Goodstadt的回答
链接:https://stackoverflow.com/questions/7110301/generic-hash-for-tuples-in-unordered-map-unordered-set?lq=1
其它可能有效的方法:
方法三的参考链接里的其它回答,还有 https://stackoverflow.com/questions/20834838/using-tuple-in-unordered-map 里的一些回答

`std::unordered_set` 是 C++ STL 中的**哈希集合容器**,它可以存储任意可哈希Hashable)且可比较相等的类型。你问“`unordered_set` 可以套的所有 STL”——理解为:**哪些 STL 类型可以作为 `unordered_set<T>` 的模板参数 `T` 成功使用?** 下面是一览表,列出 **常见 STL 类型能否被嵌套进 `unordered_set`**,并说明原因限制。 --- ### ✅ 总结一句话: > 只要类型 `T` 满足: > 1. **有默认构造函数、拷贝构造函数** > 2. **支持 `==` 运算符(用于判断键是否相等)** > 3. **存在对应的 `std::hash<T>` 特化版本(自定义哈希函数)** > > 那么它就可以放入 `unordered_set<T>` --- ## ✅ `unordered_set` 可以直接包含的 STL 类型一览表 | STL 类型 | 是否能放入 `unordered_set` | 原因 / 注意事项 | |---------|----------------------------|----------------| | `int`, `long`, `char`, `bool` | ✅ 是 | 内置整型,标准库已提供 `hash` | | `double`, `float` | ⚠️ 技术上可以,但不推荐 | 浮点数 NaN 精度问题导致行为未定义 | | `std::string` | ✅ 是 | 标准库已特化 `hash<string>` | | `const char*` | ❌ 否(危险!) | 默认按指针地址哈希,不是字符串内容;需自定义哈希 | | `std::vector<T>` | ❌ 否(无默认 hash) | 无内置 `hash<vector<T>>`,必须手动实现 | | `std::list<T>` | ❌ 否 | 无 `hash<list<T>>`,不常用作 key | | `std::deque<T>` | ❌ 否 | 无内置哈希支持 | | `std::array<T, N>` | ✅ 是(C++11 起) | 标准库提供了 `hash<array>` | | `std::pair<T1, T2>` | ❌ 否(原生不支持) | 无内置 `hash<pair>`,需自定义 | | `std::tuple<T...>` | ❌ 否(原生不支持) | 无 `hash<tuple>`,但可写递归哈希 | | `std::set<T>` | ❌ 否 | 不可哈希,无 `hash<set<T>>` | | `std::map<K,V>` | ❌ 否 | 无 `hash<map>`,复杂结构一般不用作 key | | `std::shared_ptr<T>` | ✅ 是 | 标准库提供 `hash<shared_ptr>` | | `std::unique_ptr<T>` | ⚠️ 技术上可能,但极少用 | 通常不允许复制,不适合作为 unordered_setkey | | `std::optional<T>` | ✅ 是(C++17 起) | 标准库提供 `hash<optional<T>>` | | `std::variant<T...>` | ✅ 是(C++17) | 提供了 `hash<variant>` | | `std::function<F>` | ❌ 否 | 不可复制比较,没有 `hash<function>` | --- ## 🔧 如何让“不能放”的类型也能放进 `unordered_set`? 对于不支持的类型(如 `vector`, `pair`, `tuple`),你可以: ### ✅ 方法一:提供自定义哈希函数 ```cpp struct MyHash { size_t operator()(const std::pair<int, int>& p) const { return std::hash<int>()(p.first) ^ (std::hash<int>()(p.second) << 1); } }; std::unordered_set<std::pair<int, int>, MyHash> mySet; ``` > 💡 更好的异组合方式(避免冲突): ```cpp template<typename T> void hash_combine(size_t& seed, const T& v) { seed ^= std::hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); } struct PairHash { size_t operator()(const std::pair<int, int>& p) const { size_t seed = 0; hash_combine(seed, p.first); hash_combine(seed, p.second); return seed; } }; ``` ### ✅ 方法二:使用 Boost 第三方库 Boost 提供了完整的 `hash_combine` 对 `tuple`, `vector` 等的支持。 --- ## 📌 实际可用示例代码 ```cpp #include <iostream> #include <unordered_set> #include <string> #include <array> #include <memory> #include <optional> int main() { // ✅ 内置类型 std::unordered_set<int> s1 = {1, 2, 3}; // ✅ string std::unordered_set<std::string> s2 = {"hello", "world"}; // ✅ array(固定大小) std::unordered_set<std::array<int, 2>, /*自定义hash*/> s3; s3.insert({{1, 2}}); // ✅ shared_ptr std::unordered_set<std::shared_ptr<int>> s4; auto p = std::make_shared<int>(42); s4.insert(p); // ✅ optional std::unordered_set<std::optional<int>> s5 = {1, 2, std::nullopt}; // ✅ variant using Var = std::variant<int, std::string>; std::unordered_set<Var> s6; // C++17+ s6.insert(42); s6.insert("hi"sv); return 0; } ``` --- ## ❗ 使用建议与注意事项 | 类型 | 推荐程度 | 说明 | |------|----------|------| | `int`, `string`, `array<N>` | ⭐⭐⭐⭐⭐ | 安全高效,首选 | | `pair`, `tuple` | ⭐⭐⭐⭐☆ | 需自定义哈希,但很实用(如坐标、状态) | | `vector`, `set`, `map` | ⭐☆☆☆☆ | 不推荐作 key,性能差且易出错 | | `double` | ⚠️ 谨慎使用 | 因浮点误差可能导致查找失败 | | `const char*` | ❌ 禁止直接使用 | 会比较地址而非内容 | | 自定义结构体 | ✅ 支持 | 必须实现 `==` `hash` | --- ## ✅ 自定义结构体放入 unordered_set 示例 ```cpp struct Point { int x, y; bool operator==(const Point& other) const { return x == other.x && y == other.y; } }; struct PointHash { size_t operator()(const Point& p) const { size_t seed = 0; hash_combine(seed, p.x); hash_combine(seed, p.y); return seed; } }; std::unordered_set<Point, PointHash> points; points.insert({1, 2}); ``` --- ## ✅ 总结:`unordered_set` 可“套”的所有 STL 类型分类 | 分类 | 支持情况 | 示例 | |------|----------|------| | **原生支持(无需自定义哈希)** | ✅ 直接可用 | `int`, `string`, `array`, `shared_ptr`, `optional`, `variant` | | **需自定义哈希才能使用** | ✅ 可扩展 | `pair`, `tuple`, `vector`, `Point` 等用户类型 | | **技术上可行但不推荐** | ⚠️ 小心使用 | `double`, `float`, `function` | | **无法使用无意义** | ❌ 不可用 | `list`, `map`, `deque`, `unique_ptr`(不可复制) | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值