diff --git a/libstdc++-v3/doc/html/manual/api.html b/libstdc++-v3/doc/html/manual/api.html index 2ccfc07b83e5a871ebf2c648ffdb30c6561cfdad..7a4c7d9fe620939c5ce77332387751866e7c151e 100644 --- a/libstdc++-v3/doc/html/manual/api.html +++ b/libstdc++-v3/doc/html/manual/api.html @@ -506,4 +506,7 @@ and removed in C++20: <code class="filename"><cstdalign></code>, <code class="filename"><cstdbool></code>, and <code class="filename"><ctgmath></code>. +</p><p> +Nested <code class="code">result_type</code> and <code class="code">argument_type</code> removed from +<code class="classname">std::hash</code> specializations for C++20. </p></div></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="abi.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="appendix_porting.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="backwards.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">ABI Policy and Guidelines </td><td width="20%" align="center"><a accesskey="h" href="../index.html">Home</a></td><td width="40%" align="right" valign="top"> Backwards Compatibility</td></tr></table></div></body></html> \ No newline at end of file diff --git a/libstdc++-v3/doc/xml/manual/evolution.xml b/libstdc++-v3/doc/xml/manual/evolution.xml index 6b134de0e71fcfdbf06c718fa57bc65197bee0ab..84f8ab9d2981da4f376d2fb4924850eb25accfb4 100644 --- a/libstdc++-v3/doc/xml/manual/evolution.xml +++ b/libstdc++-v3/doc/xml/manual/evolution.xml @@ -1145,6 +1145,11 @@ and removed in C++20: <filename class="headerfile"><ctgmath></filename>. </para> +<para> +Nested <code>result_type</code> and <code>argument_type</code> removed from +<classname>std::hash</classname> specializations for C++20. +</para> + </section> </section> diff --git a/libstdc++-v3/include/bits/functional_hash.h b/libstdc++-v3/include/bits/functional_hash.h index e7d8c6c20545ca375da9c23572713b4f9c4f5d32..6815edeb83330ad7a17aa4d233ed921e133d7010 100644 --- a/libstdc++-v3/include/bits/functional_hash.h +++ b/libstdc++-v3/include/bits/functional_hash.h @@ -52,43 +52,44 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template<typename _Result, typename _Arg> struct __hash_base { +#if __cplusplus < 202002L typedef _Result result_type _GLIBCXX17_DEPRECATED; typedef _Arg argument_type _GLIBCXX17_DEPRECATED; +#endif }; +#if ! _GLIBCXX_INLINE_VERSION + // Some std::hash specializations inherit this for ABI compatibility reasons. + template<typename _Tp> struct __hash_empty_base { }; +#endif + /// Primary class template hash. template<typename _Tp> struct hash; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wc++14-extensions" template<typename _Tp, typename = void> - struct __poison_hash - { - static constexpr bool __enable_hash_call = false; - private: - // Private rather than deleted to be non-trivially-copyable. - __poison_hash(__poison_hash&&); - ~__poison_hash(); - }; + constexpr bool __is_hash_enabled_for = false; template<typename _Tp> - struct __poison_hash<_Tp, __void_t<decltype(hash<_Tp>()(declval<_Tp>()))>> - { - static constexpr bool __enable_hash_call = true; - }; + constexpr bool + __is_hash_enabled_for<_Tp, + __void_t<decltype(hash<_Tp>()(declval<_Tp>()))>> + = true; +#pragma GCC diagnostic pop - // Helper struct for SFINAE-poisoning non-enum types. - template<typename _Tp, bool = is_enum<_Tp>::value> - struct __hash_enum + // Helper struct for defining disabled specializations of std::hash. + template<typename _Tp> + struct __hash_not_enabled { - private: - // Private rather than deleted to be non-trivially-copyable. - __hash_enum(__hash_enum&&); - ~__hash_enum(); + __hash_not_enabled(__hash_not_enabled&&) = delete; + ~__hash_not_enabled() = delete; }; // Helper struct for hash with enum types. - template<typename _Tp> - struct __hash_enum<_Tp, true> : public __hash_base<size_t, _Tp> + template<typename _Tp, bool = true> + struct __hash_enum : public __hash_base<size_t, _Tp> { size_t operator()(_Tp __val) const noexcept @@ -99,9 +100,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION }; /// Primary class template hash, usable for enum types only. - // Use with non-enum types still SFINAES. template<typename _Tp> - struct hash : __hash_enum<_Tp> + struct hash + : __conditional_t<__is_enum(_Tp), __hash_enum<_Tp>, __hash_not_enabled<_Tp>> { }; /// Partial specializations for pointer types. diff --git a/libstdc++-v3/include/bits/unique_ptr.h b/libstdc++-v3/include/bits/unique_ptr.h index 182173aa85714b950158427a15190491768b245f..cf24ba80a61e69c7904ac073e98b5008b608fd51 100644 --- a/libstdc++-v3/include/bits/unique_ptr.h +++ b/libstdc++-v3/include/bits/unique_ptr.h @@ -1012,11 +1012,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION /// @} relates unique_ptr /// @cond undocumented - template<typename _Up, typename _Ptr = typename _Up::pointer, - bool = __poison_hash<_Ptr>::__enable_hash_call> + template<typename _Up, typename _Ptr = typename _Up::pointer> struct __uniq_ptr_hash + : public __hash_base<size_t, _Up> #if ! _GLIBCXX_INLINE_VERSION - : private __poison_hash<_Ptr> + , private __hash_empty_base<_Ptr> #endif { size_t @@ -1025,17 +1025,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { return hash<_Ptr>()(__u.get()); } }; - template<typename _Up, typename _Ptr> - struct __uniq_ptr_hash<_Up, _Ptr, false> - : private __poison_hash<_Ptr> - { }; + template<typename _Up> + using __uniq_ptr_hash_base + = __conditional_t<__is_hash_enabled_for<typename _Up::pointer>, + __uniq_ptr_hash<_Up>, + __hash_not_enabled<typename _Up::pointer>>; /// @endcond /// std::hash specialization for unique_ptr. template<typename _Tp, typename _Dp> struct hash<unique_ptr<_Tp, _Dp>> - : public __hash_base<size_t, unique_ptr<_Tp, _Dp>>, - public __uniq_ptr_hash<unique_ptr<_Tp, _Dp>> + : public __uniq_ptr_hash_base<unique_ptr<_Tp, _Dp>> { }; #ifdef __glibcxx_make_unique // C++ >= 14 && HOSTED diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional index 2e663d13f86a6d0245c46d9c755e445856ddad2f..b8eedeec7817fb5681ec1236f40528f08d01884b 100644 --- a/libstdc++-v3/include/std/optional +++ b/libstdc++-v3/include/std/optional @@ -1732,10 +1732,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // Hash. - template<typename _Tp, typename _Up = remove_const_t<_Tp>, - bool = __poison_hash<_Up>::__enable_hash_call> - struct __optional_hash_call_base + template<typename _Tp, typename _Up = remove_const_t<_Tp>> + struct __optional_hash +#if ! _GLIBCXX_INLINE_VERSION + : public __hash_empty_base<_Up> +#endif { +#if __cplusplus < 202002L + using result_type [[__deprecated__]] = size_t; + using argument_type [[__deprecated__]] = optional<_Tp>; +#endif + size_t operator()(const optional<_Tp>& __t) const noexcept(noexcept(hash<_Up>{}(*__t))) @@ -1747,17 +1754,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } }; - template<typename _Tp, typename _Up> - struct __optional_hash_call_base<_Tp, _Up, false> {}; - template<typename _Tp> struct hash<optional<_Tp>> - : private __poison_hash<remove_const_t<_Tp>>, - public __optional_hash_call_base<_Tp> - { - using result_type [[__deprecated__]] = size_t; - using argument_type [[__deprecated__]] = optional<_Tp>; - }; + : public __conditional_t<__is_hash_enabled_for<remove_const_t<_Tp>>, + __optional_hash<_Tp>, + __hash_not_enabled<_Tp>> + { }; template<typename _Tp> struct __is_fast_hash<hash<optional<_Tp>>> : __is_fast_hash<hash<_Tp>> diff --git a/libstdc++-v3/include/std/variant b/libstdc++-v3/include/std/variant index bd0f9c3252a531b086b8466650c735f501e097be..32e539980839c72ec18ab138ffead0fd1f9f3951 100644 --- a/libstdc++-v3/include/std/variant +++ b/libstdc++-v3/include/std/variant @@ -1094,7 +1094,8 @@ namespace __variant = __gen_vtable_impl<_Array_type, std::index_sequence<>>::_S_apply(); }; - template<size_t _Np, typename _Tp> +#if ! _GLIBCXX_INLINE_VERSION + template<size_t _Nm, typename _Tp> struct _Base_dedup : public _Tp { }; template<typename _Variant, typename __indices> @@ -1103,7 +1104,9 @@ namespace __variant template<typename... _Types, size_t... __indices> struct _Variant_hash_base<variant<_Types...>, std::index_sequence<__indices...>> - : _Base_dedup<__indices, __poison_hash<remove_const_t<_Types>>>... { }; + : _Base_dedup<__indices, __hash_empty_base<remove_const_t<_Types>>>... + { }; +#endif // Equivalent to decltype(get<_Np>(as-variant(declval<_Variant>()))) template<size_t _Np, typename _Variant, @@ -1987,9 +1990,14 @@ namespace __detail::__variant #endif /// @cond undocumented - template<bool, typename... _Types> - struct __variant_hash_call_base_impl + template<typename... _Types> + struct __variant_hash { +#if __cplusplus < 202002L + using result_type [[__deprecated__]] = size_t; + using argument_type [[__deprecated__]] = variant<_Types...>; +#endif + size_t operator()(const variant<_Types...>& __t) const noexcept((is_nothrow_invocable_v<hash<decay_t<_Types>>, _Types> && ...)) @@ -2009,31 +2017,26 @@ namespace __detail::__variant return __ret; } }; - - template<typename... _Types> - struct __variant_hash_call_base_impl<false, _Types...> {}; - - template<typename... _Types> - using __variant_hash_call_base = - __variant_hash_call_base_impl<(__poison_hash<remove_const_t<_Types>>:: - __enable_hash_call &&...), _Types...>; /// @endcond template<typename... _Types> struct hash<variant<_Types...>> - : private __detail::__variant::_Variant_hash_base< - variant<_Types...>, std::index_sequence_for<_Types...>>, - public __variant_hash_call_base<_Types...> - { - using result_type [[__deprecated__]] = size_t; - using argument_type [[__deprecated__]] = variant<_Types...>; - }; + : __conditional_t<(__is_hash_enabled_for<remove_const_t<_Types>> && ...), + __variant_hash<_Types...>, + __hash_not_enabled<variant<_Types...>>> +#if ! _GLIBCXX_INLINE_VERSION + , __detail::__variant::_Variant_hash_base<variant<_Types...>, + index_sequence_for<_Types...>> +#endif + { }; template<> struct hash<monostate> { +#if __cplusplus < 202002L using result_type [[__deprecated__]] = size_t; using argument_type [[__deprecated__]] = monostate; +#endif size_t operator()(const monostate&) const noexcept diff --git a/libstdc++-v3/testsuite/20_util/optional/hash.cc b/libstdc++-v3/testsuite/20_util/optional/hash.cc index e441c87e0c950b6d9cacac44ffe523f09cc645fd..607f56459abacdf2a446ffae66c8c30f1c4d6f2e 100644 --- a/libstdc++-v3/testsuite/20_util/optional/hash.cc +++ b/libstdc++-v3/testsuite/20_util/optional/hash.cc @@ -49,3 +49,36 @@ int main() std::optional<const int> x3 = x2; VERIFY(std::hash<int>()(x) == std::hash<std::optional<const int>>()(x3)); } + +// Check for presence/absence of nested types. + +template<typename T> using res_type = typename std::hash<T>::result_type; +template<typename T> using arg_type = typename std::hash<T>::argument_type; + +template<typename Opt, typename = void> +constexpr bool has_res_type = false; +template<typename Opt> +constexpr bool has_res_type<Opt, std::void_t<res_type<Opt>>> = true; +template<typename Opt, typename = void> +constexpr bool has_arg_type = false; +template<typename Opt> +constexpr bool has_arg_type<Opt, std::void_t<arg_type<Opt>>> = true; + +template<typename T> +constexpr bool has_no_types + = ! has_res_type<std::optional<T>> && ! has_arg_type<std::optional<T>>; + +#if __cplusplus >= 202002L +// Nested types result_type and argument_type are not present in C++20 +static_assert( has_no_types<int> ); +static_assert( has_no_types<double> ); +#else +// Nested types result_type and argument_type are deprecated in C++17. +using R1 = std::hash<std::optional<int>>::result_type; // { dg-warning "deprecated" "" { target c++17_only } } +using A1 = std::hash<std::optional<int>>::argument_type; // { dg-warning "deprecated" "" { target c++17_only } } +using R2 = std::hash<std::optional<char>>::result_type; // { dg-warning "deprecated" "" { target c++17_only } } +using A2 = std::hash<std::optional<char>>::argument_type; // { dg-warning "deprecated" "" { target c++17_only } } +#endif + +// Disabled specializations do not have the nested types. +static_assert( has_no_types<S> ); diff --git a/libstdc++-v3/testsuite/20_util/optional/hash_abi.cc b/libstdc++-v3/testsuite/20_util/optional/hash_abi.cc new file mode 100644 index 0000000000000000000000000000000000000000..78e992c4e41d2e78801cc661e8d7c117e542466a --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/optional/hash_abi.cc @@ -0,0 +1,35 @@ +// { dg-do compile { target c++17 } } + +#include <optional> + +struct S { }; // std::hash<S> is a disabled specialization. + +template<typename T> +constexpr std::size_t hash_size = sizeof(std::hash<std::optional<T>>); + +template<typename... Ts> +struct MultiHash : std::hash<std::optional<Ts>>... +{ }; + +#if _GLIBCXX_INLINE_VERSION +// For the unstable ABI the size should always be one. +template<std::size_t Size, typename... Ts> +constexpr bool check_hash_size = sizeof(MultiHash<Ts...>) == 1; +#else +// For the default ABI, each std::hash<std::optional<T>> specialization has +// a base class of type __hash_empty_base<remove_cv_t<T>> and if +// the same type occurs more than once they must have unique addresses. +template<std::size_t Size, typename... Ts> +constexpr bool check_hash_size = sizeof(MultiHash<Ts...>) == Size; +#endif + +static_assert( check_hash_size<1, int> ); +static_assert( check_hash_size<1, int, long, double> ); +static_assert( check_hash_size<2, int, const int> ); +static_assert( check_hash_size<2, int, long, const int> ); + +static_assert( check_hash_size<1, S> ); +static_assert( check_hash_size<1, int, S> ); +static_assert( check_hash_size<2, int, S, const int> ); +static_assert( check_hash_size<2, int, S, const int, const S> ); +static_assert( check_hash_size<2, int, S, const S, const int> ); diff --git a/libstdc++-v3/testsuite/20_util/unique_ptr/hash/abi.cc b/libstdc++-v3/testsuite/20_util/unique_ptr/hash/abi.cc new file mode 100644 index 0000000000000000000000000000000000000000..9c0a32c49c922eede9ba7fd854a45715b66586f3 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/unique_ptr/hash/abi.cc @@ -0,0 +1,64 @@ +// { dg-do compile { target c++17 } } + +#include <memory> + +struct S { }; // std::hash<S> is a disabled specialization. + +template<typename T, typename D = std::default_delete<T>> +constexpr std::size_t hash_size = sizeof(std::hash<std::unique_ptr<T, D>>); + +template<typename... Ts> +struct MultiHash : std::hash<std::unique_ptr<Ts>>... +{ }; + +#if _GLIBCXX_INLINE_VERSION +// For the unstable ABI the size should always be one. +template<std::size_t Size, typename... Ts> +constexpr bool check_hash_size = sizeof(MultiHash<Ts...>) == 1; +#else +// For the default ABI, each std::hash<std::unique_ptr<T,D >> specialization +// has a base class of type __hash_empty_base<D::pointer> and if +// the same type occurs more than once they must have unique addresses. +template<std::size_t Size, typename... Ts> +constexpr bool check_hash_size = sizeof(MultiHash<Ts...>) == Size; +#endif + +// All these types have distinct D::pointer types, so no duplicate base classes +static_assert( check_hash_size<1, int>, "" ); +static_assert( check_hash_size<1, int, long, double>, "" ); +static_assert( check_hash_size<1, int, const int>, "" ); +static_assert( check_hash_size<1, int, long, const int>, "" ); +// Likewise for these disabled specializations: +static_assert( check_hash_size<1, S>, "" ); +static_assert( check_hash_size<1, int, S>, "" ); +static_assert( check_hash_size<1, int, S, const int>, "" ); +static_assert( check_hash_size<1, int, S, const int, const S>, "" ); +static_assert( check_hash_size<1, int, S, const S, const int>, "" ); + +// But this has two base classes of type __hash_empty_base<int*>: +static_assert( check_hash_size<2, int, int[]>, "" ); +static_assert( check_hash_size<2, int, int[], const int>, "" ); +// And this has two base classes of type __hash_not_enabled<S*>: +static_assert( check_hash_size<2, S, S[]>, "" ); +static_assert( check_hash_size<2, S, S[], const S>, "" ); + +struct Del : std::default_delete<int> { }; +using P = std::unique_ptr<int>; +using PD = std::unique_ptr<int, Del>; +using PC = std::unique_ptr<int, std::default_delete<const int>>; +using PA = std::unique_ptr<int[]>; +struct HashClash +: std::hash<P>, std::hash<PD>, std::hash<PC>, std::hash<PA> +{ }; +#if _GLIBCXX_INLINE_VERSION +static_assert(sizeof(HashClash) == 1, "No duplicate bases for unstable ABI"); +#else +static_assert(sizeof(HashClash) == 4, "four __hash_empty_base<int*> bases"); +#endif + +struct Del2 : std::default_delete<const int> { using pointer = const int*; }; +using PD2 = std::unique_ptr<int, Del2>; +struct Hash2 +: std::hash<PD>, std::hash<PD2> +{ }; +static_assert(sizeof(Hash2) == 1, "No duplicate bases"); diff --git a/libstdc++-v3/testsuite/20_util/unique_ptr/hash/types.cc b/libstdc++-v3/testsuite/20_util/unique_ptr/hash/types.cc new file mode 100644 index 0000000000000000000000000000000000000000..dbe3d8084fe8ade5f50dd7a7c12f69a46c551b65 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/unique_ptr/hash/types.cc @@ -0,0 +1,53 @@ +// { dg-do compile { target c++11 } } + +#include <memory> + + +// Check for presence/absence of nested types. + +template<typename T> using res_type = typename std::hash<T>::result_type; +template<typename T> using arg_type = typename std::hash<T>::argument_type; + +template<typename UniqPtr, typename = void> +constexpr bool +has_res_type(...) +{ return false; } + +template<typename UniqPtr> +constexpr typename std::is_void<res_type<UniqPtr>>::value_type // i.e. bool +has_res_type() +{ return true; } + +template<typename UniqPtr, typename = void> +constexpr bool +has_arg_type(...) +{ return false; } + +template<typename UniqPtr> +constexpr typename std::is_void<arg_type<UniqPtr>>::value_type // i.e. bool +has_arg_type() +{ return true; } + +template<typename UniqPtr> +constexpr bool +has_no_types() +{ return ! has_res_type<UniqPtr>() && ! has_arg_type<UniqPtr>(); } + +#if __cplusplus >= 202002L +// Nested types result_type and argument_type are not present in C++20 +static_assert( has_no_types<std::unique_ptr<int>>() ); +static_assert( has_no_types<std::unique_ptr<double>>() ); +#else +// Nested types result_type and argument_type are deprecated in C++17. +using R1 = std::hash<std::unique_ptr<int>>::result_type; // { dg-warning "deprecated" "" { target c++17_only } } +using A1 = std::hash<std::unique_ptr<int>>::argument_type; // { dg-warning "deprecated" "" { target c++17_only } } +#endif + +struct S { }; +template<> struct std::hash<S*> // disabled specialization +{ + hash(hash&&) = delete; + ~hash() = delete; +}; +// Disabled specializations do not have the nested types. +static_assert( has_no_types<std::unique_ptr<S>>(), "disabled specialization" ); diff --git a/libstdc++-v3/testsuite/20_util/variant/hash.cc b/libstdc++-v3/testsuite/20_util/variant/hash.cc index f3ab4e0d9cab12b399f7fc475175074c7531b552..52fc759fc34efbd5594e8d60f7774b15b7d40f2b 100644 --- a/libstdc++-v3/testsuite/20_util/variant/hash.cc +++ b/libstdc++-v3/testsuite/20_util/variant/hash.cc @@ -48,3 +48,37 @@ int main() std::variant<int> x2 = 42; VERIFY(std::hash<int>()(x) == std::hash<std::variant<int>>()(x2)); } + +// Check for presence/absence of nested types. + +template<typename T> using res_type = typename std::hash<T>::result_type; +template<typename T> using arg_type = typename std::hash<T>::argument_type; + +template<typename Variant, typename = void> +constexpr bool has_res_type = false; +template<typename Variant> +constexpr bool has_res_type<Variant, std::void_t<res_type<Variant>>> = true; +template<typename Variant, typename = void> +constexpr bool has_arg_type = false; +template<typename Variant> +constexpr bool has_arg_type<Variant, std::void_t<arg_type<Variant>>> = true; + +template<typename... Ts> +constexpr bool has_no_types + = ! has_res_type<std::variant<Ts...>> && ! has_arg_type<std::variant<Ts...>>; + +#if __cplusplus >= 202002L +// Nested types result_type and argument_type are not present in C++20 +static_assert( has_no_types<int> ); +static_assert( has_no_types<int, double> ); +#else +// Nested types result_type and argument_type are deprecated in C++17. +using R1 = std::hash<std::variant<int>>::result_type; // { dg-warning "deprecated" "" { target c++17_only } } +using A1 = std::hash<std::variant<int>>::argument_type; // { dg-warning "deprecated" "" { target c++17_only } } +using R2 = std::hash<std::variant<char, int>>::result_type; // { dg-warning "deprecated" "" { target c++17_only } } +using A2 = std::hash<std::variant<char, int>>::argument_type; // { dg-warning "deprecated" "" { target c++17_only } } +#endif + +// Disabled specializations do not have the nested types. +static_assert( has_no_types<S> ); +static_assert( has_no_types<int, S> ); diff --git a/libstdc++-v3/testsuite/20_util/variant/hash_abi.cc b/libstdc++-v3/testsuite/20_util/variant/hash_abi.cc new file mode 100644 index 0000000000000000000000000000000000000000..19cf72b54de61e0f835d563935d4398da04fd426 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/variant/hash_abi.cc @@ -0,0 +1,48 @@ +// { dg-options "-Wdeprecated" } +// { dg-do compile { target c++17 } } + +#include <variant> + +struct S { }; // std::hash<S> is a disabled specialization. + +// Test std::hash size + +template<typename... Ts> +constexpr std::size_t hash_size = sizeof(std::hash<std::variant<Ts...>>); + +#if _GLIBCXX_INLINE_VERSION +// For the unstable ABI the size should always be one. +template<std::size_t Size, typename... Ts> +constexpr bool check_hash_size = hash_size<Ts...> == 1; +#else +// For the default ABI, the std::hash specialization has sizeof...(Ts) +// base classes of types __hash_empty_base<remove_cv_t<Ts>>... and if +// the same type occurs more than once they must have unique addresses. +template<std::size_t Size, typename... Ts> +constexpr bool check_hash_size = hash_size<Ts...> == Size; +#endif + +static_assert( check_hash_size<1, int> ); +static_assert( check_hash_size<1, int, long, double> ); +static_assert( check_hash_size<2, int, long, int> ); +static_assert( check_hash_size<2, int, long, const int> ); +static_assert( check_hash_size<3, int, int, const int> ); + +static_assert( check_hash_size<1, S> ); +static_assert( check_hash_size<1, int, S> ); +static_assert( check_hash_size<2, int, S, int> ); +static_assert( check_hash_size<2, int, S, int, S> ); +static_assert( check_hash_size<2, int, S, S, int> ); +static_assert( check_hash_size<3, int, S, S, int, S> ); + +// For the default ABI this has two __hash_empty_base<int> base classes, +// for the unstable ABI it does not. +struct H +: std::hash<std::variant<int>>, std::hash<std::variant<long, int>> +{ }; +static_assert( sizeof(H) == hash_size<int, int, long> ); +// Likewise, even though one of the base classes is a disabled specialization. +struct HX +: std::hash<std::variant<int>>, std::hash<std::variant<S, int>> +{ }; +static_assert( sizeof(HX) == hash_size<int, S, int> );