C++ 无序映射/无序集中元组的通用哈希
为什么C++ 无序映射/无序集中元组的通用哈希,c++,c++11,tuples,unordered-map,unordered-set,C++,C++11,Tuples,Unordered Map,Unordered Set,为什么std::unordered\u map 开箱即用? 必须为元组定义一个散列函数,例如 template<> struct do_hash<tuple<int, int>> { size_t operator()(std::tuple<int, int> const& tt) const {...} }; template struct do\u hash {si
std::unordered\u map
开箱即用?
必须为元组定义一个散列函数,例如
template<> struct do_hash<tuple<int, int>>
{ size_t operator()(std::tuple<int, int> const& tt) const {...} };
template struct do\u hash
{size_t operator()(std::tuple const&tt)const{…};
(Matthieu M.)展示了如何
为boost::tuple
自动执行此操作。在不使用可变模板的情况下,是否可以对c++0x元组执行此操作
当然这应该在标准中:(在我的C++0x草案中,20.8.15
说哈希是专门用于内置类型的(包括指针,但似乎并不意味着取消对它们的引用)。它似乎还专门用于错误
,位集
,唯一
,共享
,类型索引
,字符串
,u16string
,u32string
,wstring
,向量
,以及线程::id
(外观列表!)
我还没有使用C++0x变量,所以我的格式可能有点不对劲,但这些东西可能适用于所有元组
size_t hash_combiner(size_t left, size_t right) //replacable
{ return left + 0x9e3779b9 + (right<<6) + (right>>2);}
template<int index, class...types>
struct hash_impl {
size_t operator()(size_t a, const std::tuple<types...>& t) const {
typedef typename std::tuple_element<index, std::tuple<types...>>::type nexttype;
hash_impl<index-1, types...> next;
size_t b = std::hash<nexttype>()(std::get<index>(t));
return next(hash_combiner(a, b), t);
}
};
template<class...types>
struct hash_impl<0, types...> {
size_t operator()(size_t a, const std::tuple<types...>& t) const {
typedef typename std::tuple_element<0, std::tuple<types...>>::type nexttype;
size_t b = std::hash<nexttype>()(std::get<0>(t));
return hash_combiner(a, b);
}
};
template<class...types>
struct tuple_hash<std::tuple<types...>> {
size_t operator()(const std::tuple<types...>& t) {
const size_t begin = std::tuple_size<std::tuple<types...>>::value-1;
return hash_impl<begin, types...>()(0, t);
}
}
size\u t hash\u组合器(size\u t left,size\u t right)//可重放
{返回左+0x9e3779b9+(右2);}
模板
结构hash_impl{
size_t运算符()(size_t a,const std::tuple&t)const{
typedef typename std::tuple_元素::type nexttype;
hash_impl next;
size_t b=std::hash()(std::get(t));
返回next(散列合并器(a,b),t);
}
};
模板
结构hash_impl{
size_t运算符()(size_t a,const std::tuple&t)const{
typedef typename std::tuple_元素::type nexttype;
size_t b=std::hash()(std::get(t));
返回hash_组合器(a,b);
}
};
模板
结构元组散列{
size\u t运算符()(常量std::tuple&t){
const size\u t begin=std::tuple\u size::value-1;
返回hash_impl()(0,t);
}
}
雅克观察到,从技术上讲,直接专门化std::hash
是不允许的,因为我们专门化了一个标准库模板,其声明不依赖于用户定义的类型。这在gcc 4.5上起作用,允许包含标准哈希类型的所有c++0x元组成为
unordered_map
和unordered_set
,无需进一步ado。
(我把代码放在一个头文件中,只包含它。)
函数必须位于std名称空间中,以便由
参数相关名称查找(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;
}
};
}
确保hash\u combine
调用hash\u tuple::hash
而不是std::hash
namespace hash_tuple{
namespace
{
template <class T>
inline void hash_combine(std::size_t& seed, T const& v)
{
seed ^= hash_tuple::hash<T>()(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
}
#包括
#包括
名称空间标准
{
模板
结构散列
{
size_t运算符()(元组常量和参数)常量noexcept
{
返回boost::hash_值(arg);
}
};
}
使用C++20,可以使用和计算元组的哈希而无需递归。我更喜欢依赖std::hash
,而不是手动组合哈希:
#include <cinttypes>
#include <cstddef>
#include <functional>
#include <tuple>
class hash_tuple {
template<class T>
struct component {
const T& value;
component(const T& value) : value(value) {}
uintmax_t operator,(uintmax_t n) const {
n ^= std::hash<T>()(value);
n ^= n << (sizeof(uintmax_t) * 4 - 1);
return n ^ std::hash<uintmax_t>()(n);
}
};
public:
template<class Tuple>
size_t operator()(const Tuple& tuple) const {
return std::hash<uintmax_t>()(
std::apply([](const auto& ... xs) { return (component(xs), ..., 0); }, tuple));
}
};
#包括
#包括
#包括
#包括
类哈希元组{
模板
结构组件{
常数T&value;
组件(常量T和值):值(值){}
uintmax_t运算符,(uintmax_t n)常数{
n^=std::hash()(值);
n^=n如果您使用的是C++0x,为什么不使用可变模板?(或者是否有一个具有元组的实现,但没有可变模板?@RMartinho:VC++2010与该描述相匹配(而且似乎VC++的下一个版本也可能如此)。如果std::tuple是通过手动写出1到n的模板实现的(如boost::tuple),那么我猜散列元组所需的std::散列专门化也必须以同样的方式手动写出(使用太聪明的预处理?)。Aaargh!使用left()^right()
如果您不知道该怎么做。请参阅。但请注意,XOR并不总是正确的选择。如果您希望元组包含重复的成员,则使用简单加法可能会更好。这可能是元组没有标准哈希的原因。元组不可哈希一定是一个疏忽。对于标准thoug来说为时已晚h:(@Leo很奇怪,除了vector之外没有其他容器,基本的_字符串是可散列的。(bitset技术上是容器吗?)哦,我刚才注意到:<代码>不使用变量模板。< /代码> WOOPS。因为所有元组都是不可完成的,因为元组是一个可变的模板类型。@ Alxand Rec::代码> >代码>和代码> + /代码>都是可交换的,因此是组合哈希的糟糕选择。{1,2,…,10}的突变。相反,请使用非交换组合器,例如m*left+right
,其中m是一个大奇数。是否有std::hash_组合?您的模板使用std::hash_组合和gcc 4.6.3编译。我进行了切换以避免使用boost@Bo卢:我想知道你在说什么,因为我明确地加入了hash_组合以避免boost依赖项。然后我注意到我忘了从代码中删除boost命名空间限定符…希望它仍然有效。不值得这种未定义的行为:不要在std::
中专门化那些涉及到你不拥有的东西,以及你不拥有的std::tuple
。作为一个具体的例子,这会如何可怕地破坏代码,会发生什么当标准的新迭代引入了自己的散列专门化时?如果有你的好主意的其他人引入了一个狭窄的散列
专门化,这在使用散列
的一些但不是所有地方都可见,会发生什么?这些都是具体的例子,但UB不受它们的限制。你的程序是ill格式。@allyourcode这是旧代码,但实际上建议向std命名空间添加专门化。明确禁止添加类、函数或其他定义。
namespace hash_tuple{
template <typename TT>
struct hash
{
size_t
operator()(TT const& tt) const
{
return std::hash<TT>()(tt);
}
};
}
namespace hash_tuple{
namespace
{
template <class T>
inline void hash_combine(std::size_t& seed, T const& v)
{
seed ^= hash_tuple::hash<T>()(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
}
namespace hash_tuple{
namespace
{
// 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;
}
};
}
#include <boost/functional/hash.hpp>
#include <tuple>
namespace std
{
template<typename... T>
struct hash<tuple<T...>>
{
size_t operator()(tuple<T...> const& arg) const noexcept
{
return boost::hash_value(arg);
}
};
}
#include <cinttypes>
#include <cstddef>
#include <functional>
#include <tuple>
class hash_tuple {
template<class T>
struct component {
const T& value;
component(const T& value) : value(value) {}
uintmax_t operator,(uintmax_t n) const {
n ^= std::hash<T>()(value);
n ^= n << (sizeof(uintmax_t) * 4 - 1);
return n ^ std::hash<uintmax_t>()(n);
}
};
public:
template<class Tuple>
size_t operator()(const Tuple& tuple) const {
return std::hash<uintmax_t>()(
std::apply([](const auto& ... xs) { return (component(xs), ..., 0); }, tuple));
}
};