https://gcc.gnu.org/g:1c9d93bfcd172c156fd0e94ea9990569bf46aeda
commit r16-5613-g1c9d93bfcd172c156fd0e94ea9990569bf46aeda Author: Tomasz Kamiński <[email protected]> Date: Wed Nov 19 10:29:18 2025 +0100 libstdc++: Hashing support for chrono value classes [PR110357] 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. Hashing of duration, time_point, and zoned_time only hashes the value and ignores any difference in the period, i.e. hashes of nanoseconds(2) and seconds(2) are the same. This does not affect the usages inside unordered containers, as the arguments are converted to key type first. To address that period::num and period::den could be included in the hash, however such approach will not make hashes of equal durations (2000ms, 2s) equal, so they would remain unusable for precomputed hashes. In consequence, including period in hash, would only increase runtime cost, withou any clear benefits. Futhermore, chrono::__int_hash is used when the duration 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. PR libstdc++/110357 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. Reviewed-by: Jonathan Wakely <[email protected]> Co-authored-by: Giuseppe D'Angelo <[email protected]> Signed-off-by: Tomasz Kamiński <[email protected]> Signed-off-by: Giuseppe D'Angelo <[email protected]> Diff: --- libstdc++-v3/include/bits/version.def | 13 +- libstdc++-v3/include/bits/version.h | 13 +- libstdc++-v3/include/std/chrono | 324 ++++++++++++++++++++++++++++++++ libstdc++-v3/testsuite/std/time/hash.cc | 280 +++++++++++++++++++++++++++ 4 files changed, 627 insertions(+), 3 deletions(-) diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def index 29ecf15c7e39..1fde9eef9d36 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 5901d27113d7..2ebc48b234b9 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 f0207eaae8e0..0cfad2ce1d0c 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,327 @@ namespace __detail #endif // C++20 } // namespace chrono +#if __glibcxx_chrono_cxx20 >= 202306 // C++26 + // Hash support [time.hash] + + template<typename _Tp> + concept __is_nothrow_copy_hashable = requires(const _Tp& __t) { + { hash<_Tp>{}(_Tp(__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); + }()); + + _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 + __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<_Rep> + struct hash<chrono::duration<_Rep, _Period>> + { + size_t + operator()(const chrono::duration<_Rep, _Period>& __val) const + noexcept(__is_nothrow_copy_hashable<_Rep>) + { + if constexpr (is_integral_v<_Rep>) + return chrono::__int_hash(__val.count()); + else + return hash<_Rep>{}(__val.count()); + } + }; + + 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> + 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 000000000000..ed9139bb8108 --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/hash.cc @@ -0,0 +1,280 @@ +// { 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); +#if defined __SIZEOF_INT128__ + test_hash(duration<__int128>(123456)); +#endif + 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)); +#if defined __SIZEOF_INT128__ + test_hash(sys_time<duration<__int128>>(duration<__int128>(123456))); +#endif + 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<double>(123.456)); + test(duration<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(); +}
