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.

To simplify searching, I have added `## COMMENT` before comments.

-----
> 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.
## COMMENT
We support them for optional, so this is consistent.

> * 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.
>
> libstdc++-v3/ChangeLog:
>
> * include/bits/functional_hash.h: Add a convenience hash
>  combiner.
## COMMENT
The format here is to mention modified entity name in parenthesis:
include/bits/functional_hash.h (_Hash_combiner): Define.
https://www.gnu.org/prep/standards/html_node/Style-of-Change-Logs.html#Style-of-Change-Logs

> * include/bits/version.def: Bump the feature-testing macro.
> * include/bits/version.h: Regenerate.
> * include/std/chrono: 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 |  31 ++
>  libstdc++-v3/include/bits/version.def       |   6 +
>  libstdc++-v3/include/bits/version.h         |   7 +-
>  libstdc++-v3/include/std/chrono             | 299 ++++++++++++++++++++
>  libstdc++-v3/testsuite/std/time/hash.cc     | 225 +++++++++++++++
>  5 files changed, 567 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 3626ebe816b..c0f82601b4b 100644
> --- a/libstdc++-v3/include/bits/functional_hash.h
> +++ b/libstdc++-v3/include/bits/functional_hash.h
> @@ -235,6 +235,37 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        { return hash(&__val, sizeof(__val), __hash); }
>    };
>
> +#if __cplusplus >= 201103L // C++11
> +  // A convenience hash combiner
> +  struct _Hash_combiner
> +  {
> +    static void __hash_combine(size_t&) {}
> +
> +    template<typename _Arg, typename... _Args>
> +      static void
> +      __hash_combine(size_t& __result,
> +     const _Arg& __arg,
> +     const _Args&... __args)
> +      {
> + const size_t __arg_hash = hash<_Arg>{}(__arg);
> + __result = _Hash_impl::__hash_combine(__arg_hash, __result);
> + __hash_combine(__result, __args...);
> +      }
> +
> +    static size_t __hash()
> +      { return 0; }
## COMMENT
I hope we never call this overload, so I would remove it.
+
> +    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), ...);

> + __hash_combine(__result, __args...);
> + 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 59b028c44af..2a2bbf54b3f 100644
> --- a/libstdc++-v3/include/bits/version.def
> +++ b/libstdc++-v3/include/bits/version.def
> @@ -575,6 +575,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 e465131d99b..c75cb009ada 100644
> --- a/libstdc++-v3/include/bits/version.h
> +++ b/libstdc++-v3/include/bits/version.h
> @@ -644,7 +644,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 7ffa5360728..8c6b173bc5d 100644
> --- a/libstdc++-v3/include/std/chrono
> +++ b/libstdc++-v3/include/std/chrono
> @@ -50,6 +50,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>
> @@ -3329,6 +3333,301 @@  namespace __detail
>  #endif // C++20
>    } // namespace chrono
>
> +#if __glibcxx_chrono >= 202306 // C++26
> +  // Hash support [time.hash]
> +
> +  // duration
> +  template<typename _Rep, typename _Period,
> +   typename _ActualRep = remove_cv_t<_Rep>,
> +   bool = __poison_hash<_ActualRep>::__enable_hash_call>
> +    struct __chrono_duration_hash_base
> +    {
> +      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, typename _ActualRep>
> +    struct __chrono_duration_hash_base<_Rep, _Period, _ActualRep, false>
> +    {};
> +
> +  template<typename _Rep, typename _Period>
> +    struct hash<chrono::duration<_Rep, _Period>>
> +    : private __poison_hash<remove_cv_t<_Rep>>,
> +      public __chrono_duration_hash_base<_Rep, _Period>
## COMMENT
I would just replace that with requires __is_hash_enabled_for<_Rep>. For
not formattable durations we will end up in a base template that is
disabled. And place the operator() directly in the class.

> +    {
> +      using result_type [[__deprecated__]] = size_t;
> +      using argument_type [[__deprecated__]] = chrono::duration<_Rep,
_Period>;
## COMMENT
These typedefs are only defined for __cplusplus < 202002L, and this are for
C++26 or later,
so we can simplu remove them.

> +    };
> +
> +  template<typename _Rep, typename _Period>
> +    struct __is_fast_hash<hash<chrono::duration<_Rep, _Period>>>
> +    : __is_fast_hash<hash<_Rep>>
> +    {};
> +
> +  // time_point
> +  template<typename _Clock, typename _Dur,
> +   bool = __poison_hash<_Dur>::__enable_hash_call>
> +    struct __chrono_time_point_hash_base
> +    {
> +      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 __chrono_time_point_hash_base<_Clock, _Dur, false>
> +    {};
> +
> +  template<typename _Clock, typename _Dur>
> +    struct hash<chrono::time_point<_Clock, _Dur>>
> +    : private __poison_hash<_Dur>,
> +      public __chrono_time_point_hash_base<_Clock, _Dur>
## COMMENT
I would just replace that with requires __is_hash_enabled_for<Dur>. For not
formattable durations we will end up in a base template that is disabled.
And place the operator() directly.

>
> +    {
> +      using result_type [[__deprecated__]] = size_t;
> +      using argument_type [[__deprecated__]] =
chrono::time_point<_Clock, _Dur>;
> +    };
> +
> +  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>
> +    : public __hash_base<size_t, chrono::day>
## COMMENT
We also do not need__hash_base, that only reason for existence is to
provide result_type and argument_type members, that we define only before
C++20.

> +    {
> +      size_t operator()(chrono::day __val) const noexcept
> +      { return static_cast<unsigned>(__val); }
> +    };
> +
> +  // month
> +  template<>
> +    struct hash<chrono::month>
> +    : public __hash_base<size_t, 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>
> +    : public __hash_base<size_t, 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>
> +    : public __hash_base<size_t, 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>
> +    : public __hash_base<size_t, 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>
> +    : public __hash_base<size_t, 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>
> +    : public __hash_base<size_t, 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>
> +    : public __hash_base<size_t, 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>
> +    : public __hash_base<size_t, 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>
> +    : public __hash_base<size_t, 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>
> +    : public __hash_base<size_t, 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>
> +    : public __hash_base<size_t, 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>
> +    : public __hash_base<size_t, 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>
> +    : public __hash_base<size_t, 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,
> +   typename _ActualDuration =
> +     typename chrono::zoned_time<_Duration, _TimeZonePtr>::duration,
> +   bool = (__poison_hash<_ActualDuration>::__enable_hash_call &&
> +   __poison_hash<_TimeZonePtr>::__enable_hash_call)>
> +    struct __chrono_zoned_time_hash_base
> +    {
> +      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,
> +   typename _ActualDuration>
> +    struct __chrono_zoned_time_hash_base
> + <_Duration, _TimeZonePtr, _ActualDuration, false>
> +    {};
> +
> +  template<typename _Duration, typename _TimeZonePtr>
> +    struct hash<chrono::zoned_time<_Duration, _TimeZonePtr>>
> +    : private __poison_hash<_Duration>,
> +      private __poison_hash<_TimeZonePtr>,
> +      public __chrono_zoned_time_hash_base<_Duration, _TimeZonePtr>
## COMMENT
Same comment regarding using requires for __is_hash_enabled_for<_Duration>
&& __is_hash_enabled_for<_TimeZonePtr>.

> +    {
> +      using result_type [[__deprecated__]] = size_t;
> +      using argument_type [[__deprecated__]] =
> + chrono::zoned_time<_Duration, _TimeZonePtr>;
## COMMENT
These are no longer present since C++20.

> +    };
> +
> +  template<typename _Duration, typename _TimeZonePtr>
> +    struct __is_fast_hash<hash<chrono::zoned_time<_Duration,
_TimeZonePtr>>>
> +    : bool_constant<__is_fast_hash<hash<_Duration>>::value &&
> +            __is_fast_hash<hash<_TimeZonePtr>>::value>
> +    {};
> +
> +  // leap_second
> +  template<>
> +    struct hash<chrono::leap_second>
> +    : public __hash_base<size_t, chrono::leap_second>
>
> +  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)));
> +}
## COMMENT
I would add a negative test here, to check if hash specializations are not
enabled if _Rep or _TimeZonePtr is not hashable. As one of the
differentiation between enabled and disabled hash is
is_default_constructible_v, I would check static assert for
!is_default_constructible_v.

> +
> +int main()
> +{
> +  test01();
> +  test02();
> +  test03();
> +}
--

Reply via email to