惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
SegmentFault 最新的问题
爱范儿
爱范儿
博客园 - 三生石上(FineUI控件)
Microsoft Security Blog
Microsoft Security Blog
Google DeepMind News
Google DeepMind News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
GbyAI
GbyAI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V
V2EX
酷 壳 – CoolShell
酷 壳 – CoolShell
量子位
博客园_首页
T
Tailwind CSS Blog
aimingoo的专栏
aimingoo的专栏
A
About on SuperTechFans
T
The Blog of Author Tim Ferriss
Stack Overflow Blog
Stack Overflow Blog
Recent Announcements
Recent Announcements
P
Proofpoint News Feed
博客园 - 司徒正美
有赞技术团队
有赞技术团队
Engineering at Meta
Engineering at Meta
Last Week in AI
Last Week in AI
MongoDB | Blog
MongoDB | Blog

博客园 - 木木ちゃん

Lab 1: Utilities Lab 0: 准备工作 Chapter 1: Operating system interfaces 操作系统接口 线性注意力机制学习笔记 关于二分查找的简单思考 DWDP: 在NVL72上的高性能分布式权重数据并行 NCCL EP 论文解读 Megatron-LM-Moe 论文阅读笔记 (ISCA 2025) Chimera: Communication Fusion for Hybrid Parallelism in Large Language Models (Sigcomm'25) Stellar: 阿里新一代云AI RDMA网络 deepseek-v3.2-exp: 节前发版之打工人的悲鸣 关于Leetcode 812题的简单思考 减少KVCache google_test Linux相关配置+双系统安装记录 SkipList Conga:分布式拥塞感知与负载均衡数据中心(Sigcomm'14) Megatron-LM Efficient AI training system MiniOB Lab4: join tables & group by MiniOB Lab3 布隆过滤器:原理与leveldb中的实现 第三章 SQL入门
tuple hash: 尝试在set/map中使用tuple
木木ちゃん · 2025-07-06 · via 博客园 - 木木ちゃん

Tuple Hash: 基于boost库的学习

首先我们知道,对于一个哈希的重载,std::hash 是一个结构体,我们需要重载他的 () 来实现自己的哈希函数。我们的大概想法如下:

template<typename T>
struct std::hash<T> {
    std::size_t operator()(const T & arg) const {
        return my_hash(arg);
    }
};

那么对于一个 tuple,因为他的模板特化时模板参数不定长,我们就需要采用变长模板来实现我们的 hash 结构体重载 operator()。这样我们大概为:

template<typename ... T>
struct std::hash<std::tuple<T...>> {
    std::size_t operator()(const std::tuple<T...> & arg) const {
        // variadic template deal;
    }
};

这样我们就需要思考如何递归地解决我们的模板展开。我们需要通过实现递归式地展开直到到达模板的末尾,通过 hash_combine的方式来一个个组合成我们的最终hash 值。首先我们先定义 hash_combine, 他将获得 tuple 中含有的基本类型

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

接下来我们需要一个个地取值放入其中。我们可以通过:

template<std::size_t I = 0, typename ... T>
typename std::enable_if<(I < sizeof...(T)), void>::type
my_hash(std::size_t & seed, const std::tuple<T...> & tup) {
    hash_combine(seed, std::get<I>(tup));
    my_hash<I + 1, T...>(seed, tup);
}

首先学习一下 std::enable_if<B,T>。在 cppreference 上你可以看到:

If B is true, std::enable_if has a public member typedef type, equal to T; otherwise, there is no member typedef.

This metafunction is a convenient way to leverage SFINAE prior to C++20's concepts, in particular for conditionally removing functions from the candidate set based on type traits, allowing separate function overloads or specializations based on those different type traits.

当 B(表达式)为 true 的时候,我们的 std::enable_if 将会具有 public 成员类型 T, 否则将不会具有类型的定义。这样,我们的元方程就可以使用 SFINAE("Substitution Failure Is Not An Error",替代失败并不是错误) 特性(其实我还没弄懂这到底是个什么),特别是基于类型萃取来讲函数从候选集合中移除(这里涉及到重载决议),允许不同的函数重载/特化基于不同的类型萃取。

说了一大段废话,其实就是:符合 B 的时候,函数出现并返回 T 类型。否则函数不会出现(不会被重载选择)。

这样在最后我们补充一个递归终点:

template <std::size_t Index = 0, typename... Types>
inline typename std::enable_if<Index == sizeof...(Types), void>::type
my_hash(std::size_t&, const std::tuple<Types...>&) {
}

这样,在 index 没有到达模板参数长度时,我们将采取前面的函数,持续递归。否则什么都不做。
最后补充上我们的结构体。在 C++11 的标准下就可以运行。可以尝试使用查看:

compiler explorer 1 C++-11 version
compiler explorer 2 boost mimic

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

template <std::size_t Index = 0, typename... Types>
inline typename std::enable_if<Index == sizeof...(Types), void>::type
my_hash(std::size_t&, const std::tuple<Types...>&) {
}

template<std::size_t I = 0, typename ... T>
typename std::enable_if<(I < sizeof...(T)), void>::type
my_hash(std::size_t & seed, const std::tuple<T...> & tup) {
    hash_combine(seed, std::get<I>(tup));
    my_hash<I + 1, T...>(seed, tup);
}

template <typename ... T>
struct std::hash<std::tuple<T...> > {
    std::size_t operator()(const tuple<T...> & tup) const {
        std::size_t seed = 0;
        my_hash(seed, tup);
        return seed;
    }
};

实际上这已经很接近我们 boost 库的实现,在 boost 库中,我们的 enable_if 采用的是通过 std::tuple_size<T>::size 来实现。

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

template<std::size_t I, typename T>
typename std::enable_if<(I == std::tuple_size<T>::value), void>::type
    hash_combine_tuple_like(std::size_t &, T const &)
{
}

template<std::size_t I, typename T>
typename std::enable_if<(I < std::tuple_size<T>::value), void>::type
    hash_combine_tuple_like(std::size_t & seed, T const & v) 
{
    hash_combine(seed, std::get<I>(v));
    hash_combine_tuple_like<I+1>(seed, v);
}

template <class T>
std::size_t hash_tuple(T const & v) {
    std::size_t seed = 0;
    hash_combine_tuple_like<0>(seed, v);
    return seed;
}

template <class T>
struct hash_tup {
    std::size_t operator()(T const & val) const {
        return hash_tuple<T>(val);
    }
};

参考资料: boost container.hash

后面再找时间学习闭包展开和折叠表达式。