Skip to content

Commit fb39274

Browse files
committed
Add tuple hash
1 parent 335e66c commit fb39274

File tree

3 files changed

+83
-0
lines changed

3 files changed

+83
-0
lines changed

include/ankerl/unordered_dense.h

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,62 @@ struct hash<Enum, typename std::enable_if<std::is_enum<Enum>::value>::type> {
328328
}
329329
};
330330

331+
template <typename... Args>
332+
struct tuple_hash_helper {
333+
template <typename Arg>
334+
[[nodiscard]] constexpr static auto calc_buf_size() {
335+
if constexpr (std::has_unique_object_representations_v<Arg>) {
336+
return sizeof(Arg);
337+
} else {
338+
return sizeof(hash<Arg>{}(std::declval<Arg>()));
339+
}
340+
}
341+
342+
// Reads data from back to front. We do this so there's no need for bswap when multiple
343+
// bytes are read (on little endian). This should be a tiny bit faster.
344+
template <typename Arg>
345+
[[nodiscard]] constexpr static auto put(std::byte* pos, Arg const& arg) -> std::byte* {
346+
if constexpr (std::has_unique_object_representations_v<Arg>) {
347+
pos -= sizeof(Arg);
348+
std::memcpy(pos, &arg, sizeof(Arg));
349+
return pos;
350+
} else {
351+
auto x = hash<Arg>{}(arg);
352+
pos -= sizeof(x);
353+
std::memcpy(pos, &x, sizeof(x));
354+
return pos;
355+
}
356+
}
357+
358+
// Creates a buffer that holds all the data from each element of the tuple. If possible we memcpy the data directly. If
359+
// not, we hash the object and use this for the array. Size of the array is known at compile time, and memcpy is optimized
360+
// away, so filling the buffer is highly efficient. Finally, call wyhash with this buffer.
361+
template <typename T, std::size_t... Idx>
362+
[[nodiscard]] static auto calc_hash(T const& t, std::index_sequence<Idx...>) noexcept -> uint64_t {
363+
std::array<std::byte, (calc_buf_size<Args>() + ...)> tmp_buffer;
364+
auto* buf_ptr = tmp_buffer.data() + tmp_buffer.size();
365+
((buf_ptr = put(buf_ptr, std::get<Idx>(t))), ...);
366+
// at this point, buf_ptr==tmp_buffer.data()
367+
return ankerl::unordered_dense::detail::wyhash::hash(tmp_buffer.data(), tmp_buffer.size());
368+
}
369+
};
370+
371+
template <typename... Args>
372+
struct hash<std::tuple<Args...>> : tuple_hash_helper<Args...> {
373+
using is_avalanching = void;
374+
auto operator()(std::tuple<Args...> const& t) const noexcept -> uint64_t {
375+
return tuple_hash_helper<Args...>::calc_hash(t, std::index_sequence_for<Args...>{});
376+
}
377+
};
378+
379+
template <typename A, typename B>
380+
struct hash<std::pair<A, B>> : tuple_hash_helper<A, B> {
381+
using is_avalanching = void;
382+
auto operator()(std::pair<A, B> const& t) const noexcept -> uint64_t {
383+
return tuple_hash_helper<A, B>::calc_hash(t, std::index_sequence_for<A, B>{});
384+
}
385+
};
386+
331387
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
332388
# define ANKERL_UNORDERED_DENSE_HASH_STATICCAST(T) \
333389
template <> \

test/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ test_sources = [
7373
'unit/swap.cpp',
7474
'unit/transparent.cpp',
7575
'unit/try_emplace.cpp',
76+
'unit/tuple_hash.cpp',
7677
'unit/unique_ptr.cpp',
7778
'unit/unordered_set.cpp',
7879
'unit/vectorofmaps.cpp',

test/unit/tuple_hash.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include <ankerl/unordered_dense.h>
2+
3+
#include <app/doctest.h>
4+
5+
TEST_CASE("tuple_hash") {
6+
auto m = ankerl::unordered_dense::map<std::pair<int, std::string>, int>();
7+
auto pair_hash = ankerl::unordered_dense::hash<std::pair<int, std::string>>{};
8+
REQUIRE(pair_hash(std::pair<int, std::string>{1, "a"}) != pair_hash(std::pair<int, std::string>{1, "b"}));
9+
10+
m.try_emplace({1, "a"}, 23);
11+
m.try_emplace({1, "b"}, 42);
12+
REQUIRE(m.size() == 2U);
13+
}
14+
15+
TEST_CASE("good_tuple_hash") {
16+
auto hashes = ankerl::unordered_dense::set<uint64_t>();
17+
18+
auto t = std::tuple<uint8_t, uint8_t, uint8_t>();
19+
for (size_t i = 0; i < 256 * 256; ++i) {
20+
std::get<0>(t) = static_cast<uint8_t>(i);
21+
std::get<2>(t) = static_cast<uint8_t>(i / 256);
22+
hashes.emplace(ankerl::unordered_dense::hash<decltype(t)>{}(t));
23+
}
24+
25+
REQUIRE(hashes.size() == 256 * 256);
26+
}

0 commit comments

Comments
 (0)