@@ -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 <> \
0 commit comments