HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Hashing a tuple in C++17

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
hashingtuplestackoverflow

Problem

C++ doesn't supply a 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:

#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.