On Thu, Nov 20, 2025 at 7:36 PM Tomasz Kaminski <[email protected]> wrote:

>
>
> On Thu, Nov 20, 2025 at 6:37 PM Jonathan Wakely <[email protected]>
> wrote:
>
>> On Thu, 20 Nov 2025 at 14:28 +0100, Tomasz Kamiński wrote:
>> >This patch implements P2592R3 Hashing support for std::chrono value
>> classes.
>> >
>> >To avoid the know issues with current hashing of integer types (see
>> PR104945),
>> >we use chrono::__int_hash function that hash the bytes of representation,
>> >instead of hash<T>, as the later simply cast to value. Currently
>> _Hash_impl
>> >it used, but we should consider replacing it (see PR55815) before C++26
>> ABI
>> >is made stable. The function is declared inside <chrono> header and
>> chrono
>> >namespace, to make sure that only chrono components would be affected by
>> >such change. Finally, chrono::__int_hash is made variadic, to support
>> >combining hashes of multiple integers.
>> >
>> >To reduce the number of calls to hasher (defined out of line), the
>> calendar
>> >types are packed into single unsigned integer value. This is done by
>> >chrono::__hash helper, that calls:
>> >* chrono::__as_int to cast the value of single component, to unsigned
>> integer
>> >  with size matching the one used by internal representation: unsigned
>> short
>> >  for year/weekday_indexed, and unsigned char in all other cases.
>> >* chrono::__pack_ints to pack integers (if more than one) into single
>> integer
>> >  by performing bit shift operations
>> >* chrono::__int_hash to hash the value produced by above.
>> >
>> >For type chrono::duration, __int_hash is used when the representation is
>> >integral type, and for other types (floating point due special handling
>> >of +/-0.0 and user defined types) we delegate to hash specialization.
>> >This is automatically picked up by time_point, that delegates to hasher
>> >of duration. Similarly for leap_second that is specified to use integer
>> >durations, we simply hash representations of date() and value(). Finally
>> >zoned_time in addition to handling integer durations as described above,
>> >we also use __int_hash for const time_zone* (if used), as hash<T*> have
>> >similar problems as hash specialization for integers. This is limited
>> >only to _TimeZonePtr being const time_zone* (default), as user can define
>> >hash specializations for raw pointers to they zones.
>> >
>> >As accessing the representation for duration requires calling count()
>> >method that returns a copy of representation by value, the noexcept
>> >specification of the hasher needs to take into consideration copy
>> >constructor of duration. Similar reasoning applies for time_since_epoch
>> >for time_points, and get_sys_time, get_time_zone for zoned_time.
>> >For all this cases we use internal __is_nothrow_copy_hashable concept.
>> >
>> >Finally support for zoned_time is provided only for CXX11 string ABI,
>> >__cpp_lib_chrono feature test macro cannot be bumped if COW string are
>> used.
>> >To indicate presence of hasher for remaining types this patch also bumps
>> >the internal __glibcxx_chrono_cxx20 macro, and uses it as guard to new
>> >features.
>> >
>> >libstdc++-v3/ChangeLog:
>> >
>> >       * include/bits/version.def (chrono, chrono_cxx20): Bump values.
>> >       * include/bits/version.h: Regenerate.
>> >       * include/std/chrono (__is_nothrow_copy_hashable)
>> >       (chrono::__pack_ints, chrono::__as_int, chrono::__int_hash)
>> >       (chrono::__hash): Define.
>> >       (std::hash): Define partial specialization for duration,
>> time_point,
>> >       and zoned_time, and full specializations for calendar types and
>> >       leap_second.
>> >       (std::__is_fast_hash): Define partial specializations for
>> duration,
>> >       time_point, zoned_time.
>> >       * testsuite/std/time/hash.cc: New test.
>> >
>> >Co-authored-by: Giuseppe D'Angelo <[email protected]>
>> >Signed-off-by: Tomasz Kamiński <[email protected]>
>> >Signed-off-by: Giuseppe D'Angelo <[email protected]>
>> >---
>> >v3 reimplements __pack_ints so the size of produced type,
>> >is not dependend on order of arguments.
>> >
>> >Tested on x86_64-linux. Additionally std/time/hash.cc tested with
>> >all standard modes and both string ABIs. OK for trunk?
>> >
>> > libstdc++-v3/include/bits/version.def   |  13 +-
>> > libstdc++-v3/include/bits/version.h     |  13 +-
>> > libstdc++-v3/include/std/chrono         | 328 ++++++++++++++++++++++++
>> > libstdc++-v3/testsuite/std/time/hash.cc | 281 ++++++++++++++++++++
>> > 4 files changed, 632 insertions(+), 3 deletions(-)
>> > create mode 100644 libstdc++-v3/testsuite/std/time/hash.cc
>> >
>> >diff --git a/libstdc++-v3/include/bits/version.def
>> b/libstdc++-v3/include/bits/version.def
>> >index 29ecf15c7e3..1fde9eef9d3 100644
>> >--- a/libstdc++-v3/include/bits/version.def
>> >+++ b/libstdc++-v3/include/bits/version.def
>> >@@ -593,6 +593,12 @@ ftms = {
>> >
>> > ftms = {
>> >   name = chrono;
>> >+  values = {
>> >+    v = 202306;
>> >+    cxxmin = 26;
>> >+    hosted = yes;
>> >+    cxx11abi = yes;
>> >+  };
>> >   values = {
>> >     v = 201907;
>> >     cxxmin = 20;
>> >@@ -607,8 +613,13 @@ ftms = {
>> > };
>> >
>> > ftms = {
>> >-  // Unofficial macro for C++20 chrono features supported for old
>> string ABI.
>> >+  // Unofficial macro for chrono features supported for old string ABI.
>> >   name = chrono_cxx20;
>> >+  values = {
>> >+    v = 202306;
>> >+    cxxmin = 26;
>> >+    no_stdname = yes;
>> >+  };
>> >   values = {
>> >     v = 201800;
>> >     cxxmin = 20;
>> >diff --git a/libstdc++-v3/include/bits/version.h
>> b/libstdc++-v3/include/bits/version.h
>> >index 5901d27113d..2ebc48b234b 100644
>> >--- a/libstdc++-v3/include/bits/version.h
>> >+++ b/libstdc++-v3/include/bits/version.h
>> >@@ -666,7 +666,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
>> >@@ -681,7 +686,11 @@
>> > #undef __glibcxx_want_chrono
>> >
>> > #if !defined(__cpp_lib_chrono_cxx20)
>> >-# if (__cplusplus >= 202002L)
>> >+# if (__cplusplus >  202302L)
>> >+#  define __glibcxx_chrono_cxx20 202306L
>> >+#  if defined(__glibcxx_want_all) ||
>> defined(__glibcxx_want_chrono_cxx20)
>> >+#  endif
>> >+# elif (__cplusplus >= 202002L)
>> > #  define __glibcxx_chrono_cxx20 201800L
>> > #  if defined(__glibcxx_want_all) ||
>> defined(__glibcxx_want_chrono_cxx20)
>> > #  endif
>> >diff --git a/libstdc++-v3/include/std/chrono
>> b/libstdc++-v3/include/std/chrono
>> >index f0207eaae8e..b25e9979254 100644
>> >--- a/libstdc++-v3/include/std/chrono
>> >+++ b/libstdc++-v3/include/std/chrono
>> >@@ -64,6 +64,9 @@
>> > # include <bits/shared_ptr.h>
>> > # include <bits/unique_ptr.h>
>> > #endif
>> >+#if __glibcxx_chrono_cxx20 >= 202306L // C++26
>> >+# include <bits/functional_hash.h>
>> >+#endif
>> >
>> > namespace std _GLIBCXX_VISIBILITY(default)
>> > {
>> >@@ -3322,6 +3325,331 @@ namespace __detail
>> > #endif // C++20
>> >   } // namespace chrono
>> >
>> >+#if __glibcxx_chrono_cxx20 >= 202306 // C++26
>> >+  // Hash support [time.hash]
>> >+
>> >+  template<typename _Tp, typename _Td = remove_cv_t<_Tp>>
>> >+    concept __is_nothrow_copy_hashable = requires(const _Td& __t) {
>> >+      { hash<_Td>{}(_Td(__t)) } noexcept -> same_as<size_t>;
>> >+    };
>> >+
>> >+ namespace chrono {
>> >+
>> >+   template<typename _T1, typename... _Ts>
>> >+     [[__gnu__::__always_inline__]]
>> >+     constexpr auto
>> >+     __pack_ints(_T1 __v1, _Ts... __vs)
>> >+     {
>> >+       using _ResT = decltype([] {
>> >+         constexpr size_t __tsize = (sizeof(_T1) + ... + sizeof(_Ts));
>> >+       if constexpr (__tsize <= 1)
>> >+         return static_cast<unsigned char>(0);
>> >+       else if constexpr (__tsize <= 2)
>> >+         return static_cast<__UINT16_TYPE__>(0);
>> >+       else if constexpr (__tsize <= 4)
>> >+         return static_cast<__UINT32_TYPE__>(0);
>> >+       else if constexpr (__tsize <= 8)
>> >+         return static_cast<__UINT64_TYPE__>(0);
>> >+       else
>> >+         static_assert(__tsize <= 8);
>> >+       }());
>>
>> Does this mean std::chrono::duration<__int128> can't be hashed?
>>
> No, durations representations are not packed, they go directly to
> __int_hash,
> and not chrono::__hash
> There would be no point of doing so. We pack only calendar types.
>
>>
>> >+
>> >+       _ResT __res = __v1;
>> >+       ((__res = (__res <= (sizeof(_Ts) * __CHAR_BIT__) |
>> _ResT(__vs))), ...);
>> >+       return __res;
>> >+     }
>> >+
>> >+   template<typename _Tp>
>> >+     [[__gnu__::__always_inline__]]
>> >+     constexpr auto
>> >+     __as_int(_Tp __val)
>> >+     {
>> >+       if constexpr (is_same_v<_Tp, year>)
>> >+       return static_cast<unsigned short>(static_cast<int>(__val));
>> >+       else if constexpr (is_same_v<_Tp, month> || is_same_v<_Tp, day>)
>> >+       return static_cast<unsigned char>(static_cast<unsigned>(__val));
>> >+       else if constexpr (is_same_v<_Tp, weekday>)
>> >+       return static_cast<unsigned char>(__val.c_encoding());
>> >+       else if constexpr (is_same_v<_Tp, weekday_indexed>)
>> >+       return __pack_ints(chrono::__as_int(__val.weekday()),
>> >+                          static_cast<unsigned char>(__val.index()));
>> >+       else if constexpr (is_same_v<_Tp, weekday_last>)
>> >+       return chrono::__as_int(__val.weekday());
>> >+       else
>> >+       static_assert(false);
>> >+     }
>> >+
>> >+   template<typename _Arg, typename... _Args>
>> >+     size_t
>>
>> Is this intentionally not 'inline' ?
>>
> Yes,  other functions are very thin wrappers over value, this one contains
> out of
> line calls.
>
>>
>> >+     __int_hash(_Arg __arg, _Args... __args)
>> >+     {
>> >+       static_assert((is_integral_v<_Arg> && ... &&
>> is_integral_v<_Args>));
>> >+
>> >+       // TODO consider using a better quality hasher
>> >+       using _Hasher = _Hash_impl;
>> >+       size_t __result = _Hasher::hash(__arg);
>> >+       ((__result = _Hasher::__hash_combine(__args, __result)), ...);
>> >+       return __result;
>> >+     }
>> >+
>> >+   template<typename... _Tps>
>> >+     [[__gnu__::__always_inline__]]
>> >+     inline size_t
>> >+     __hash(_Tps... __vals)
>> >+     {
>> >+       if constexpr (sizeof...(_Tps) == 1)
>> >+       return chrono::__int_hash(chrono::__as_int(__vals)...);
>> >+       else
>> >+       {
>> >+         auto __packed =
>> chrono::__pack_ints(chrono::__as_int(__vals)...);
>> >+         return chrono::__int_hash(__packed);
>> >+       }
>> >+     }
>> >+ } // namespace chrono
>> >+
>> >+  // duration
>> >+  template<typename _Rep, typename _Period>
>> >+    requires __is_hash_enabled_for<remove_cv_t<_Rep>>
>> >+    struct hash<chrono::duration<_Rep, _Period>>
>> >+    {
>> >+    private:
>> >+      using _ActualRep = remove_cv_t<_Rep>;
>>
>> Do we allow duration<const int> ?!?
>>
> Giuseppe added them in the test, and they complied, so I guess we do.
>
Relevant comment from original PR:
-

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

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


>
>> >+
>> >+    public:
>> >+      size_t
>> >+      operator()(const chrono::duration<_Rep, _Period>& __val) const
>> >+      noexcept(__is_nothrow_copy_hashable<_Rep>)
>> >+      {
>> >+      if constexpr (is_integral_v<_ActualRep>)
>> >+        return chrono::__int_hash(__val.count());
>> >+      else
>> >+        return hash<_ActualRep>{}(__val.count());
>> >+      }
>>
>> This definition means that 1s and 1ms and 1ns will hash to the same
>> value. That's not necessarily a problem, but could be surprising.
>>
>> I think we did discuss it with Giuseppe, and I guess we decided not to
>> hash the period, since that's what his original patches did? I think
>> since you can't store 1ms and 1ns in the same std::unordered_xxx
>> container, it doesn't matter. You'd need to convert them to a common
>> type, and then 1'000'000ns and 1ns would hash to different values.
>>
>> If we wanted to hash the period, it would be quite easy, as we could
>> just combine val.count() and Period::type::num and Period::type::den.
>> Then people might be surprised that 1s and 1000ms don't hash the same
>> though.
>>
>> So I think not hashing the period is probably the right answer.
>>
>> >+    };
>> >+
>> >+  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_hashable<_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 chrono::__hash(__val); }
>> >+    };
>> >+
>> >+  // month
>> >+  template<>
>> >+    struct hash<chrono::month>
>> >+    {
>> >+      size_t
>> >+      operator()(chrono::month __val) const noexcept
>> >+      { return chrono::__hash(__val); }
>> >+    };
>> >+
>> >+  // year
>> >+  template<>
>> >+    struct hash<chrono::year>
>> >+    {
>> >+      size_t
>> >+      operator()(chrono::year __val) const noexcept
>> >+      { return chrono::__hash(__val); }
>> >+    };
>> >+
>> >+  // weekday
>> >+  template<>
>> >+    struct hash<chrono::weekday>
>> >+    {
>> >+      size_t
>> >+      operator()(chrono::weekday __val) const noexcept
>> >+      { return chrono::__hash(__val); }
>> >+    };
>> >+
>> >+  // weekday_indexed
>> >+  template<>
>> >+    struct hash<chrono::weekday_indexed>
>> >+    {
>> >+      size_t
>> >+      operator()(chrono::weekday_indexed __val) const noexcept
>> >+      { return chrono::__hash(__val); }
>> >+    };
>> >+
>> >+  // weekday_last
>> >+  template<>
>> >+    struct hash<chrono::weekday_last>
>> >+    {
>> >+      size_t
>> >+      operator()(chrono::weekday_last __val) const noexcept
>> >+      { return chrono::__hash(__val); }
>> >+    };
>> >+
>> >+  // month_day
>> >+  template<>
>> >+    struct hash<chrono::month_day>
>> >+    {
>> >+      size_t
>> >+      operator()(chrono::month_day __val) const noexcept
>> >+      { return chrono::__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 chrono::__hash(__val.month()); }
>> >+    };
>> >+
>> >+  // month_weekday
>> >+  template<>
>> >+    struct hash<chrono::month_weekday>
>> >+    {
>> >+      size_t
>> >+      operator()(chrono::month_weekday __val) const noexcept
>> >+      { return chrono::__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 chrono::__hash(__val.month(), __val.weekday_last()); }
>> >+    };
>> >+
>> >+  // year_month
>> >+  template<>
>> >+    struct hash<chrono::year_month>
>> >+    {
>> >+      size_t
>> >+      operator()(chrono::year_month __val) const noexcept
>> >+      { return chrono::__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 chrono::__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 chrono::__hash(__val.year(), __val.month()); }
>> >+    };
>> >+
>> >+  // year_month_weekday
>> >+  template<>
>> >+    struct hash<chrono::year_month_weekday>
>> >+    {
>> >+      size_t
>> >+      operator()(chrono::year_month_weekday __val) const noexcept
>> >+      {
>> >+      return chrono::__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 chrono::__hash(__val.year(), __val.month(),
>> >+                            __val.weekday_last());
>> >+      }
>> >+    };
>> >+
>> >+#if _GLIBCXX_HOSTED
>> >+#if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI
>> >+  // 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>>
>> >+    {
>> >+    private:
>> >+      using _ActualDuration =
>> >+      typename chrono::zoned_time<_Duration, _TimeZonePtr>::duration;
>> >+
>> >+    public:
>> >+      size_t
>> >+      operator()(const chrono::zoned_time<_Duration, _TimeZonePtr>&
>> __val) const
>> >+      noexcept(__is_nothrow_copy_hashable<_ActualDuration>
>> >+             && __is_nothrow_copy_hashable<_TimeZonePtr>)
>> >+      {
>> >+      const auto __iduration = [&] {
>> >+        const _ActualDuration __sd =
>> __val.get_sys_time().time_since_epoch();
>> >+        if constexpr (is_integral_v<typename _ActualDuration::rep>)
>> >+          return __sd.count();
>> >+        else
>> >+          return hash<_ActualDuration>{}(__sd);
>> >+      }();
>> >+
>> >+      const auto __izone = [&] {
>> >+        const _TimeZonePtr __tz = __val.get_time_zone();
>> >+        if constexpr (is_same_v<_TimeZonePtr, const chrono::time_zone*>)
>> >+          return reinterpret_cast<uintptr_t>(__tz);
>> >+        else
>> >+          return hash<_TimeZonePtr>{}(__tz);
>> >+      }();
>> >+
>> >+      return chrono::__int_hash(__iduration, __izone);
>> >+      }
>> >+    };
>> >+
>> >+  template<typename _Duration, typename _TimeZonePtr>
>> >+    struct __is_fast_hash<hash<chrono::zoned_time<_Duration,
>> _TimeZonePtr>>>
>> >+    : __and_<__is_fast_hash<hash<
>> >+             typename chrono::zoned_time<_Duration,
>> _TimeZonePtr>::duration>>,
>> >+           __is_fast_hash<hash<_TimeZonePtr>>>
>> >+    {};
>> >+
>> >+  // leap_second
>> >+  template<>
>> >+    struct hash<chrono::leap_second>
>> >+    {
>> >+      size_t
>> >+      operator()(chrono::leap_second __val) const noexcept
>> >+      {
>> >+      return chrono::__int_hash(
>> >+               __val.date().time_since_epoch().count(),
>> >+               __val.value().count());
>> >+      }
>> >+    };
>> >+#endif // _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI
>> >+#endif // _GLIBCXX_HOSTED
>> >+#endif // __glibcxx_chrono_cxx20 >= 202306
>> >+
>> > #ifdef __glibcxx_chrono_cxx20
>> >   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..8d3b54c0931
>> >--- /dev/null
>> >+++ b/libstdc++-v3/testsuite/std/time/hash.cc
>> >@@ -0,0 +1,281 @@
>> >+// { dg-do run { target c++26 } }
>> >+
>> >+#include <chrono>
>> >+#include <unordered_set>
>> >+#include <limits.h>
>> >+#include <testsuite_hooks.h>
>> >+
>> >+#if _GLIBCXX_USE_CXX11_ABI
>> >+#  if !defined(__cpp_lib_chrono)
>> >+#    error "__cpp_lib_chrono not defined"
>> >+#  elif __cpp_lib_chrono < 202306L
>> >+#    error "Wrong value for __cpp_lib_chrono"
>> >+#  endif
>> >+#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)));
>> >+  static_assert(std::__is_fast_hash<T>::value);
>> >+  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]);
>> >+
>> >+#if _GLIBCXX_USE_CXX11_ABI
>> >+  // 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);
>> >+#endif
>> >+}
>> >+
>> >+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;
>> >+
>> >+  static constexpr
>> >+  auto test_hash = []<typename T>(const T& t)
>> >+  {
>> >+    static_assert(noexcept(std::hash<T>{}(t)));
>> >+  };
>> >+
>> >+  static constexpr
>> >+  auto test = []<typename D>(const D& d)
>> >+  {
>> >+    test_hash(d);
>> >+    test_hash(sys_time<D>(d));
>> >+#if _GLIBCXX_USE_CXX11_ABI
>> >+    test_hash(zoned_time<D>(sys_time<D>(d)));
>> >+#endif
>> >+  };
>> >+
>> >+
>> >+  test(duration<int>(123));
>> >+  test(duration<int, std::ratio<1, 1000>>(123));
>> >+  test(duration<int, std::ratio<1000, 1>>(123));
>> >+  test(duration<volatile double>(123.456));
>> >+  test(duration<arithmetic_wrapper<int>>(arithmetic_wrapper<int>(123)));
>> >+
>> >+  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>>>);
>> >+
>> >+#if _GLIBCXX_USE_CXX11_ABI
>> >+  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>>>>);
>> >+#endif
>> >+}
>> >+
>> >+int main()
>> >+{
>> >+  test01();
>> >+  test02();
>> >+  test03();
>> >+  test04();
>> >+}
>> >--
>> >2.51.1
>> >
>> >
>>
>>

Reply via email to