Hi Tomasz,

Thank you for reviewing the original patch!

I'm attaching a second version, hopefully addressing what you've highlighed. I've also pushed it on Forge:

https://forge.sourceware.org/gcc/gcc-TEST/pulls/52


On 24/04/2025 15:30, Tomasz Kaminski wrote:
Hi,

I am reattaching the original patch below, as I wasn't on the mailing list when it was sent.
Thank you for submitting the patch and apologies for the late response.

The major comment I have is that these are new C++26 classes, so we can use requires __is_hash_enabled_for<_Tp> and define only enabled specialization. In other cases we will be using a primary template that is disabled.

Also, argument_type and result_type typedefs are no longer present since C++20, so you can remove them, and also remove __hash_base base classes that were responsible for providing them.

Sounds good to me, I've done this cleanup.

+
 > +    template<typename _Arg, typename... _Args>
 > +      static size_t __hash(const _Arg& __arg, const _Args&... __args)
 > +      {
 > + const size_t __arg_hash = hash<_Arg>{}(__arg);
 > + size_t __result = _Hash_impl::hash(__arg_hash);
## COMMENT
I think you can implement this using fold expression, as:
(_Hash_impl::__hash_combine(hash<_Arg>{}(__args), __result), ...);

Ok, I just wanted to make it work on C++11, but there's no real need for that I guess...

 > + __hash_combine(__result, __args...);
 > + return __result;
 > +      }
 > +  };
 > +#endif // C++11
 > +

Thank you,

--
Giuseppe D'Angelo
From e32c3b6180b9bf5b25158f33c05ab0cd889d2318 Mon Sep 17 00:00:00 2001
From: Giuseppe D'Angelo <giuseppe.dang...@kdab.com>
Date: Wed, 4 Sep 2024 12:57:51 +0200
Subject: [PATCH] libstdc++: hashing support for chrono value classes (P2592)
 [PR110357]

This commit implements [time.hash], added by P2592 for C++26.
The implementation of the various hash specializations is
mostly straightforward:

* duration hashes its representation (not the period);
* time_point hashes its duration;
* the calendaring classes (year, month, day, etc.) hash their
  values;
* zoned_time hashes the time zone pointer and its time point.

There are however a couple of challenges:

* The noexcept specifications for hashing duration, time_point,
  zoned_time are slightly more convoluted than expected, as
  their getters are noexcept(false) (e.g. calling count() on a
  duration will copy the representation and that may throw);

* [time.duration] says that "Rep shall be an arithmetic type or a
  class emulating an arithmetic type". Technically speaking, this
  means that `const int` is a valid Rep; but we can't use
  hash<const int>.

  I'm not sure if this is deliberate or not (cf. LWG951, LWG953),
  but I've decided to support it nonetheless.

* zoned_time and the calendar classes that combine several
  parts (e.g. month_day) need a hash combiner. The one
  available in _Hash_impl works on objects representations,
  not values, and given the nature of the calendar classes
  I'm very afraid that I may accidentally be hashing padding
  bits. Therefore, I've added a helper convenience combiner.

	PR libstdc++/110357

libstdc++-v3/ChangeLog:

	* include/bits/functional_hash.h (_Hash_combiner): Define.
	* include/bits/version.def (__cpp_lib_chrono): Bump the
	feature-testing macro.
	* include/bits/version.h: Regenerate.
	* include/std/chrono (hash): Add std::hash specializations
	  for the value classes in namespace chrono.
	* testsuite/std/time/hash.cc: New test.

Signed-off-by: Giuseppe D'Angelo <giuseppe.dang...@kdab.com>
---
 libstdc++-v3/include/bits/functional_hash.h |  20 ++
 libstdc++-v3/include/bits/version.def       |   6 +
 libstdc++-v3/include/bits/version.h         |   7 +-
 libstdc++-v3/include/std/chrono             | 242 ++++++++++++++++++
 libstdc++-v3/testsuite/std/time/hash.cc     | 257 ++++++++++++++++++++
 5 files changed, 531 insertions(+), 1 deletion(-)
 create mode 100644 libstdc++-v3/testsuite/std/time/hash.cc

diff --git a/libstdc++-v3/include/bits/functional_hash.h b/libstdc++-v3/include/bits/functional_hash.h
index e84c9ee04be..944859db3e4 100644
--- a/libstdc++-v3/include/bits/functional_hash.h
+++ b/libstdc++-v3/include/bits/functional_hash.h
@@ -238,6 +238,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       { return hash(&__val, sizeof(__val), __hash); }
   };
 
+#if __cplusplus >= 201703L // C++17
+  // A convenience hash combiner
+  struct _Hash_combiner
+  {
+    static void
+    __hash_combine(size_t __h, size_t& __result)
+    {
+      __result = _Hash_impl::__hash_combine(__h, __result);
+    }
+
+    template<typename _Arg, typename... _Args>
+      static size_t __hash(const _Arg& __arg, const _Args&... __args)
+      {
+	size_t __result = hash<_Arg>{}(__arg);
+	(__hash_combine(hash<_Args>{}(__args), __result), ...);
+	return __result;
+      }
+  };
+#endif // C++11
+
   /// Specialization for float.
   template<>
     struct hash<float> : public __hash_base<size_t, float>
diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index 9ab22cc519f..676999b149e 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -581,6 +581,12 @@ ftms = {
 
 ftms = {
   name = chrono;
+  values = {
+    v = 202306;
+    cxxmin = 26;
+    hosted = yes;
+    cxx11abi = yes;
+  };
   values = {
     v = 201907;
     cxxmin = 20;
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index 371a7ba3b1a..c3a0597c94f 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -651,7 +651,12 @@
 #undef __glibcxx_want_boyer_moore_searcher
 
 #if !defined(__cpp_lib_chrono)
-# if (__cplusplus >= 202002L) && _GLIBCXX_USE_CXX11_ABI && _GLIBCXX_HOSTED
+# if (__cplusplus >  202302L) && _GLIBCXX_USE_CXX11_ABI && _GLIBCXX_HOSTED
+#  define __glibcxx_chrono 202306L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_chrono)
+#   define __cpp_lib_chrono 202306L
+#  endif
+# elif (__cplusplus >= 202002L) && _GLIBCXX_USE_CXX11_ABI && _GLIBCXX_HOSTED
 #  define __glibcxx_chrono 201907L
 #  if defined(__glibcxx_want_all) || defined(__glibcxx_want_chrono)
 #   define __cpp_lib_chrono 201907L
diff --git a/libstdc++-v3/include/std/chrono b/libstdc++-v3/include/std/chrono
index 8eb9fd9baac..5419d5868a1 100644
--- a/libstdc++-v3/include/std/chrono
+++ b/libstdc++-v3/include/std/chrono
@@ -56,6 +56,10 @@
 # include <bits/unique_ptr.h>
 #endif
 
+#if __cplusplus > 202302L
+# include <bits/functional_hash.h>
+#endif
+
 #define __glibcxx_want_chrono
 #define __glibcxx_want_chrono_udls
 #include <bits/version.h>
@@ -3345,6 +3349,244 @@ namespace __detail
 #endif // C++20
   } // namespace chrono
 
+#if __glibcxx_chrono >= 202306 // C++26
+  // Hash support [time.hash]
+
+  // duration
+  template<typename _Rep, typename _Period>
+    requires __is_hash_enabled_for<remove_cv_t<_Rep>>
+    struct hash<chrono::duration<_Rep, _Period>>
+    {
+      using _ActualRep = remove_cv_t<_Rep>;
+      size_t
+      operator()(const chrono::duration<_Rep, _Period>& __val) const
+      noexcept(
+	is_nothrow_copy_constructible_v<_Rep> &&
+	noexcept(hash<_ActualRep>{}(std::declval<_Rep>()))
+      )
+      {
+	return hash<_ActualRep>{}(__val.count());
+      }
+    };
+
+  template<typename _Rep, typename _Period>
+    struct __is_fast_hash<hash<chrono::duration<_Rep, _Period>>>
+    : __is_fast_hash<hash<remove_cv_t<_Rep>>>
+    {};
+
+  // time_point
+  template<typename _Clock, typename _Dur>
+    requires __is_hash_enabled_for<_Dur>
+    struct hash<chrono::time_point<_Clock, _Dur>>
+    {
+      size_t
+      operator()(const chrono::time_point<_Clock, _Dur>& __val) const
+      noexcept(
+	is_nothrow_copy_constructible_v<_Dur> &&
+	noexcept(hash<_Dur>{}(std::declval<_Dur>()))
+      )
+      {
+	return hash<_Dur>{}(__val.time_since_epoch());
+      }
+    };
+
+  template<typename _Clock, typename _Dur>
+    struct __is_fast_hash<hash<chrono::time_point<_Clock, _Dur>>>
+    : __is_fast_hash<hash<_Dur>>
+    {};
+
+  // day
+  template<>
+    struct hash<chrono::day>
+    {
+      size_t operator()(chrono::day __val) const noexcept
+      { return static_cast<unsigned>(__val); }
+    };
+
+  // month
+  template<>
+    struct hash<chrono::month>
+    {
+      size_t operator()(chrono::month __val) const noexcept
+      { return static_cast<unsigned>(__val); }
+    };
+
+  // year
+  template<>
+    struct hash<chrono::year>
+    : public __hash_base<size_t, chrono::year>
+    {
+      size_t operator()(chrono::year __val) const noexcept
+      {
+	const int __tmp = static_cast<int>(__val);
+	return static_cast<size_t>(__tmp);
+      }
+    };
+
+  // weekday
+  template<>
+    struct hash<chrono::weekday>
+    {
+      size_t operator()(chrono::weekday __val) const noexcept
+      { return static_cast<size_t>(__val.c_encoding()); }
+    };
+
+  // weekday_indexed
+  template<>
+    struct hash<chrono::weekday_indexed>
+    {
+      size_t operator()(chrono::weekday_indexed __val) const noexcept
+      {
+	return _Hash_combiner::__hash(__val.weekday(), __val.index());
+      }
+    };
+
+  // weekday_last
+  template<>
+    struct hash<chrono::weekday_last>
+    {
+      size_t operator()(chrono::weekday_last __val) const noexcept
+      { return static_cast<size_t>(__val.weekday().c_encoding()); }
+    };
+
+  // month_day
+  template<>
+    struct hash<chrono::month_day>
+    {
+      size_t operator()(chrono::month_day __val) const noexcept
+      {
+	return _Hash_combiner::__hash(__val.month(), __val.day());
+      }
+    };
+
+  // month_day_last
+  template<>
+    struct hash<chrono::month_day_last>
+    {
+      size_t operator()(chrono::month_day_last __val) const noexcept
+      { return static_cast<unsigned>(__val.month()); }
+    };
+
+  // month_weekday
+  template<>
+    struct hash<chrono::month_weekday>
+    {
+      size_t operator()(chrono::month_weekday __val) const noexcept
+      {
+	return _Hash_combiner::__hash(__val.month(), __val.weekday_indexed());
+      }
+    };
+
+  // month_weekday_last
+  template<>
+    struct hash<chrono::month_weekday_last>
+    {
+      size_t
+      operator()(chrono::month_weekday_last __val) const noexcept
+      {
+	return _Hash_combiner::__hash(__val.month(), __val.weekday_last());
+      }
+    };
+
+  // year_month
+  template<>
+    struct hash<chrono::year_month>
+    {
+      size_t operator()(chrono::year_month __val) const noexcept
+      {
+	return _Hash_combiner::__hash(__val.year(), __val.month());
+      }
+    };
+
+  // year_month_day
+  template<>
+    struct hash<chrono::year_month_day>
+    {
+      size_t operator()(chrono::year_month_day __val) const noexcept
+      {
+	return _Hash_combiner::__hash(__val.year(),
+	                              __val.month(),
+	                              __val.day());
+      }
+    };
+
+  // year_month_day_last
+  template<>
+    struct hash<chrono::year_month_day_last>
+    {
+      size_t
+      operator()(chrono::year_month_day_last __val) const noexcept
+      {
+	return _Hash_combiner::__hash(__val.year(),
+	                              __val.month_day_last());
+      }
+    };
+
+  // year_month_weekday
+  template<>
+    struct hash<chrono::year_month_weekday>
+    {
+      size_t operator()(chrono::year_month_weekday __val) const noexcept
+      {
+	return _Hash_combiner::__hash(__val.year(),
+	                              __val.month(),
+	                              __val.weekday_indexed());
+      }
+    };
+
+  // year_month_weekday_last
+  template<>
+    struct hash<chrono::year_month_weekday_last>
+    {
+      size_t
+      operator()(chrono::year_month_weekday_last __val) const noexcept
+      {
+	return _Hash_combiner::__hash(__val.year(),
+	                            __val.month(),
+	                            __val.weekday_last());
+      }
+    };
+
+  // zoned_time
+  template<typename _Duration, typename _TimeZonePtr>
+    requires __is_hash_enabled_for<typename chrono::zoned_time<_Duration, _TimeZonePtr>::duration> && 
+	     __is_hash_enabled_for<_TimeZonePtr>
+    struct hash<chrono::zoned_time<_Duration, _TimeZonePtr>>
+    {
+      using _ActualDuration =
+	typename chrono::zoned_time<_Duration, _TimeZonePtr>::duration;
+
+      size_t
+      operator()
+      (const chrono::zoned_time<_Duration, _TimeZonePtr>& __val)
+      const
+      noexcept(
+	is_nothrow_copy_constructible_v<_ActualDuration> &&
+	noexcept(hash<_ActualDuration>{}(std::declval<_ActualDuration>())) &&
+	is_nothrow_copy_constructible_v<_TimeZonePtr> &&
+	noexcept(hash<_TimeZonePtr>{}(std::declval<_TimeZonePtr>()))
+      )
+      {
+	return _Hash_combiner::__hash(__val.get_sys_time(),
+	                              __val.get_time_zone());
+      }
+    };
+
+  template<typename _Duration, typename _TimeZonePtr>
+    struct __is_fast_hash<hash<chrono::zoned_time<_Duration, _TimeZonePtr>>>
+    : bool_constant<__is_fast_hash<hash<typename chrono::zoned_time<_Duration, _TimeZonePtr>::duration>>::value &&
+	            __is_fast_hash<hash<_TimeZonePtr>>::value>
+    {};
+
+  // leap_second
+  template<>
+    struct hash<chrono::leap_second>
+    {
+      size_t operator()(chrono::leap_second __val) const noexcept
+      { return hash<chrono::seconds>{}(__val.value()); }
+    };
+#endif // __glibcxx_chrono >= 202306
+
 #if __cplusplus >= 202002L
   inline namespace literals
   {
diff --git a/libstdc++-v3/testsuite/std/time/hash.cc b/libstdc++-v3/testsuite/std/time/hash.cc
new file mode 100644
index 00000000000..9710376fd39
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/hash.cc
@@ -0,0 +1,257 @@
+// { dg-do run { target c++26 } }
+// { dg-require-effective-target tzdb }
+
+#include <chrono>
+#include <unordered_set>
+#include <limits.h>
+#include <testsuite_hooks.h>
+
+#if !defined(__cpp_lib_chrono)
+#error "__cpp_lib_chrono not defined"
+#elif __cpp_lib_chrono < 202306L
+#error "Wrong value for __cpp_lib_chrono"
+#endif
+
+template <typename T>
+struct arithmetic_wrapper
+{
+  arithmetic_wrapper() = default;
+  arithmetic_wrapper(T t) : t(t) {}
+  friend bool operator==(arithmetic_wrapper, arithmetic_wrapper) = default;
+  T t;
+};
+
+template <typename T>
+struct std::hash<arithmetic_wrapper<T>>
+{
+  size_t operator()(arithmetic_wrapper<T> val) const noexcept
+  { return std::hash<T>{}(val.t); }
+};
+
+template <typename T>
+struct non_hashable_arithmetic_wrapper
+{
+  non_hashable_arithmetic_wrapper() = default;
+  non_hashable_arithmetic_wrapper(T t) : t(t) {}
+  friend bool operator==(non_hashable_arithmetic_wrapper, non_hashable_arithmetic_wrapper) = default;
+  T t;
+};
+
+template <typename T>
+constexpr bool is_hash_poisoned = !std::is_default_constructible_v<std::hash<T>>;
+
+template <typename T>
+void test_unordered_set(const T& t)
+{
+  std::unordered_set<T> set;
+
+  set.insert(t);
+  VERIFY(set.size() == 1);
+  VERIFY(set.contains(t));
+
+  set.erase(t);
+  VERIFY(set.size() == 0);
+  VERIFY(!set.contains(t));
+}
+
+template <typename T>
+void test_hash(const T& t)
+{
+  static_assert(noexcept(std::hash<T>{}(t)));
+  test_unordered_set(t);
+}
+
+void test01()
+{
+  using namespace std::chrono;
+  using namespace std::literals::chrono_literals;
+
+  // duration
+  test_hash(-999s);
+  test_hash(1234ms);
+  test_hash(duration<double>(123.45));
+  using AWint = arithmetic_wrapper<int>;
+  test_hash(duration<AWint>(AWint(1234)));
+  using AWdouble = arithmetic_wrapper<double>;
+  test_hash(duration<AWdouble>(AWdouble(123.45)));
+
+  // time_point
+  test_hash(sys_seconds(1234s));
+  test_hash(sys_time<duration<double>>(duration<double>(123.45)));
+  test_hash(utc_seconds(1234s));
+  test_hash(local_days(days(1234)));
+  test_hash(system_clock::now());
+  test_hash(steady_clock::now());
+  test_hash(utc_clock::now());
+  test_hash(gps_clock::now());
+
+  // day
+  test_hash(1d);
+  test_hash(0d);
+  test_hash(255d);
+  test_hash(1234d);
+  test_hash(day(UINT_MAX));
+
+  // month
+  test_hash(January);
+  test_hash(September);
+  test_hash(month(0u));
+  test_hash(month(255u));
+  test_hash(month(1234u));
+  test_hash(month(UINT_MAX));
+
+  // year
+  test_hash(2024y);
+  test_hash(0y);
+  test_hash(year::min());
+  test_hash(year::max());
+  test_hash(year(INT_MAX));
+  test_hash(year(INT_MIN));
+
+  // weekday
+  test_hash(Monday);
+  test_hash(Thursday);
+  test_hash(weekday(255u));
+  test_hash(weekday(UINT_MAX));
+
+  // weekday_indexed
+  test_hash(Monday[0u]);
+  test_hash(Monday[7u]);
+  test_hash(Monday[1234u]);
+  test_hash(weekday(1234u)[0u]);
+
+  // weekday_last
+  test_hash(Monday[last]);
+  test_hash(Friday[last]);
+  test_hash(weekday(1234u)[last]);
+
+  // month_day
+  test_hash(March / 3);
+  test_hash(March / 31);
+  test_hash(February / 31);
+  test_hash(February / 1234);
+  test_hash(month(1234u) / 1);
+
+  // month_day_last
+  test_hash(March / last);
+  test_hash(month(1234u) / last);
+
+  // month_weekday
+  test_hash(March / Tuesday[2u]);
+  test_hash(month(1234u) / Tuesday[2u]);
+  test_hash(March / weekday(1234u)[2u]);
+  test_hash(March / Tuesday[1234u]);
+
+  // month_weekday_last
+  test_hash(April / Sunday[last]);
+  test_hash(month(1234u) / Tuesday[last]);
+  test_hash(April / weekday(1234u)[last]);
+
+  // year_month
+  test_hash(2024y / August);
+  test_hash(1'000'000y / August);
+  test_hash(2024y / month(1234u));
+
+  // year_month_day
+  test_hash(2024y / August / 31);
+  test_hash(-10y / March / 5);
+  test_hash(2024y / February / 31);
+  test_hash(1'000'000y / March / 5);
+  test_hash(2024y / month(1234u) / 5);
+  test_hash(2024y / March / 1234);
+
+  // year_month_day_last
+  test_hash(2024y / August / last);
+  test_hash(1'000'000y / August / last);
+  test_hash(2024y / month(1234u) / last);
+
+  // year_month_weekday
+  test_hash(2024y / August / Tuesday[2u]);
+  test_hash(-10y / August / Tuesday[2u]);
+  test_hash(1'000'000y / August / Tuesday[2u]);
+  test_hash(2024y / month(1234u) / Tuesday[2u]);
+  test_hash(2024y / August / weekday(1234u)[2u]);
+  test_hash(2024y / August / Tuesday[1234u]);
+
+  // year_month_weekday_last
+  test_hash(2024y / August / Tuesday[last]);
+  test_hash(-10y / August / Tuesday[last]);
+  test_hash(1'000'000y / August / Tuesday[last]);
+  test_hash(2024y / month(1234u) / Tuesday[last]);
+  test_hash(2024y / August / weekday(1234u)[last]);
+
+  // zoned_time
+  test_hash(zoned_seconds("Europe/Rome", sys_seconds(1234s)));
+  test_hash(zoned_time("Europe/Rome", system_clock::now()));
+
+  // leap_second
+  for (leap_second l : get_tzdb().leap_seconds)
+    test_hash(l);
+}
+
+void test02()
+{
+  using namespace std::chrono;
+  using namespace std::literals::chrono_literals;
+
+  {
+    std::unordered_set<milliseconds> set;
+    set.insert(2000ms);
+    VERIFY(set.contains(2000ms));
+    VERIFY(set.contains(2s));
+    VERIFY(!set.contains(1234ms));
+    VERIFY(!set.contains(1234s));
+  }
+  {
+    using TP = sys_time<milliseconds>;
+    std::unordered_set<TP> set;
+    set.insert(TP(2000ms));
+    VERIFY(set.contains(TP(2000ms)));
+    VERIFY(set.contains(sys_seconds(2s)));
+    VERIFY(!set.contains(TP(1234ms)));
+    VERIFY(!set.contains(sys_seconds(1234s)));
+  }
+}
+
+void test03()
+{
+  using namespace std::chrono;
+
+  const auto test = []<typename T>(const T& t)
+  {
+    static_assert(noexcept(std::hash<T>{}(t)));
+  };
+
+  test(duration<const int>(123));
+  test(duration<const int, std::ratio<1, 1000>>(123));
+  test(duration<const int, std::ratio<1000, 1>>(123));
+  test(duration<const volatile double>(123.456));
+  test(duration<const arithmetic_wrapper<int>>(arithmetic_wrapper<int>(123)));
+}
+
+void test04()
+{
+  using namespace std::chrono;
+
+  static_assert(!is_hash_poisoned<duration<int>>);
+  static_assert(!is_hash_poisoned<duration<double>>);
+  static_assert(!is_hash_poisoned<duration<arithmetic_wrapper<int>>>);
+  static_assert(!is_hash_poisoned<duration<arithmetic_wrapper<double>>>);
+  static_assert(is_hash_poisoned<duration<non_hashable_arithmetic_wrapper<int>>>);
+  static_assert(is_hash_poisoned<duration<non_hashable_arithmetic_wrapper<double>>>);
+
+  static_assert(!is_hash_poisoned<zoned_time<duration<int>>>);
+  static_assert(!is_hash_poisoned<zoned_time<duration<double>>>);
+  static_assert(!is_hash_poisoned<zoned_time<duration<arithmetic_wrapper<int>>>>);
+  static_assert(!is_hash_poisoned<zoned_time<duration<arithmetic_wrapper<double>>>>);
+  static_assert(is_hash_poisoned<zoned_time<duration<non_hashable_arithmetic_wrapper<int>>>>);
+  static_assert(is_hash_poisoned<zoned_time<duration<non_hashable_arithmetic_wrapper<double>>>>);
+}
+
+int main()
+{
+  test01();
+  test02();
+  test03();
+  test04();
+}
-- 
2.34.1

Attachment: smime.p7s
Description: S/MIME Cryptographic Signature

Reply via email to