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
smime.p7s
Description: S/MIME Cryptographic Signature