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);
+ }());
+
+ _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<remove_cv_t<_Rep>>
+ struct hash<chrono::duration<_Rep, _Period>>
+ {
+ private:
+ using _ActualRep = remove_cv_t<_Rep>;
+
+ 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());
+ }
+ };
+
+ 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