patterncppMinor
Hashing a tuple in C++17
Viewed 0 times
hashingtuplestackoverflow
Problem
C++ doesn't supply a
This was my first time seriously using template meta-progamming. I tried to make my code work no matter the cv-ness of the variable, or whether it was an rvalue or an lvalue. I'm not sure on the efficiency of execution, but I believe the compiler can optimize it down to simple arithmetic on hash values.
hash.h
```
#pragma once
#include
#include
#include
#include
#include
namespace utils {
template
struct hash
{
template
std::size_t operator()(Type && v) const {
return std::hash()(std::forward(v));
}
};
namespace internal {
// A simple algorithm for combining two hash values,
// algorithm from boost: http://www.boost.org/doc/libs/1_61_0/doc/html/hash/reference.html#boost.hash_combine
constexpr inline std::size_t hash_combine(std::size_t hash1, std::size_t hash2) {
return hash1 ^ (hash2 * 0x9e3779b9 + (hash1 > 2));
}
// Necessary to hold a value so that we can use the fold operator
struct hash_combine_t
{
const std::size_t value;
};
// % because it's a non-commutative operator that has been used for things other than
// it's "intended" purpose before (e.g. string formatting)
constexpr hash_combine_t operator%(const hash_combine_t &lhs, const hash_combine_t &rhs) {
return { hash_combine(lhs.value, rhs.value) };
}
template
constexpr std::size_t hash_combine_impl(Ts&&... args) {
return (hash_combine_t{ static_cast(args) } % ...).value;
}
template
constexpr std::size_t hash_all_impl(Ts&&... args) {
return hash_combine_impl(utils::hash>()(std::forward(args))...);
}
}
template
constexpr std::size_t hash_combine(Ts&&... args) {
return internal::hash_combine_impl(args...);
}
template
constexpr std::size_t hash_all(Ts&&... args) {
std::hash>, so I decided to implement one. However, I was viewing this as more of a hash library than extensions to std, so I placed it all under the namespace utils.This was my first time seriously using template meta-progamming. I tried to make my code work no matter the cv-ness of the variable, or whether it was an rvalue or an lvalue. I'm not sure on the efficiency of execution, but I believe the compiler can optimize it down to simple arithmetic on hash values.
hash.h
```
#pragma once
#include
#include
#include
#include
#include
namespace utils {
template
struct hash
{
template
std::size_t operator()(Type && v) const {
return std::hash()(std::forward(v));
}
};
namespace internal {
// A simple algorithm for combining two hash values,
// algorithm from boost: http://www.boost.org/doc/libs/1_61_0/doc/html/hash/reference.html#boost.hash_combine
constexpr inline std::size_t hash_combine(std::size_t hash1, std::size_t hash2) {
return hash1 ^ (hash2 * 0x9e3779b9 + (hash1 > 2));
}
// Necessary to hold a value so that we can use the fold operator
struct hash_combine_t
{
const std::size_t value;
};
// % because it's a non-commutative operator that has been used for things other than
// it's "intended" purpose before (e.g. string formatting)
constexpr hash_combine_t operator%(const hash_combine_t &lhs, const hash_combine_t &rhs) {
return { hash_combine(lhs.value, rhs.value) };
}
template
constexpr std::size_t hash_combine_impl(Ts&&... args) {
return (hash_combine_t{ static_cast(args) } % ...).value;
}
template
constexpr std::size_t hash_all_impl(Ts&&... args) {
return hash_combine_impl(utils::hash>()(std::forward(args))...);
}
}
template
constexpr std::size_t hash_combine(Ts&&... args) {
return internal::hash_combine_impl(args...);
}
template
constexpr std::size_t hash_all(Ts&&... args) {
Solution
I think we can definitely simplify that:
Forward Declaration.
Hash All Types
Have a version for Tuples.
Do all the work in a helper function.
Tests
#include
#include
#include
#include Forward Declaration.
template
std::size_t tupleHash(Tuple const& tuple, std::index_sequence const&);Hash All Types
template
std::size_t hashValue(T const& value)
{
// SFINAE kicks in here for tuples.
// There is no std::hash that works for tuples.
// So this candidate will be ignored if you use a tuple.
std::hash hasher;
return hasher(value);
}Have a version for Tuples.
template
std::size_t hashValue(std::tuple const& value)
{
return tupleHash(value, std::make_index_sequence());
}
// Special case the empty tuple (as Args... can not be the empty list).
std::size_t hashValue(std::tuple<> const& value)
{
// Not an expert in hashing.
// Do some appropriate thing to get a value;
return 1;
}Do all the work in a helper function.
template
std::size_t tupleHash(Tuple const& tuple, std::index_sequence const&)
{
// Not an expert in hashing.
// Not sure 0 is a good seed (or this algorithm is good) solely here for demo purpose.
std::size_t result = 0;
for(auto const& hash: {hashValue(std::get(tuple))...})
{
result ^= hash + 0x9e3779b9 + (result>2);
}
return result;
};Tests
int main()
{
auto tp = std::make_tuple(1,2,"Loki");
std::cout << hashValue(tp) << "\n";
auto ttp = std::make_tuple(4,tp,8,tp);
std::cout << hashValue(ttp) << "\n";
auto et = std::make_tuple();
std::cout << hashValue(et) << "\n";
}Code Snippets
#include <tuple>
#include <iostream>
#include <functional>
#include <type_traits>template<typename Tuple, std::size_t... ids>
std::size_t tupleHash(Tuple const& tuple, std::index_sequence<ids...> const&);template<typename T>
std::size_t hashValue(T const& value)
{
// SFINAE kicks in here for tuples.
// There is no std::hash that works for tuples.
// So this candidate will be ignored if you use a tuple.
std::hash<T> hasher;
return hasher(value);
}template<typename... Args>
std::size_t hashValue(std::tuple<Args...> const& value)
{
return tupleHash(value, std::make_index_sequence<sizeof...(Args)>());
}
// Special case the empty tuple (as Args... can not be the empty list).
std::size_t hashValue(std::tuple<> const& value)
{
// Not an expert in hashing.
// Do some appropriate thing to get a value;
return 1;
}template<typename Tuple, std::size_t... ids>
std::size_t tupleHash(Tuple const& tuple, std::index_sequence<ids...> const&)
{
// Not an expert in hashing.
// Not sure 0 is a good seed (or this algorithm is good) solely here for demo purpose.
std::size_t result = 0;
for(auto const& hash: {hashValue(std::get<ids>(tuple))...})
{
result ^= hash + 0x9e3779b9 + (result<<6) + (result>>2);
}
return result;
};Context
StackExchange Code Review Q#136770, answer score: 5
Revisions (0)
No revisions yet.