I've pushed this now.

On Wed, 6 Nov 2024 at 15:50, Jonathan Wakely <jwak...@redhat.com> wrote:
>
> This attempts to simplify and clean up our std::hash code. The primary
> benefit is improved diagnostics for users when they do something wrong
> involving std::hash or unordered containers. An additional benefit is
> that for the unstable ABI (--enable-symvers=gnu-versioned-namespace) we
> can reduce the memory footprint of several std::hash specializations.
>
> In the current design, __hash_enum is a base class of the std::hash
> primary template, but the partial specialization of __hash_enum for
> non-enum types is disabled.  This means that if a user forgets to
> specialize std::hash for their class type (or forgets to use a custom
> hash function for unordered containers) they get error messages about
> std::__hash_enum not being constructible.  This is confusing when there
> is no enum type involved: why should users care about __hash_enum not
> being constructible if they're not trying to hash enums?
>
> This change makes the std::hash primary template only derive from
> __hash_enum when the template argument type is an enum. Otherwise, it
> derives directly from a new class template, __hash_not_enabled. This new
> class template defines the deleted members that cause a given std::hash
> specialization to be a disabled specialization (as per P0513R0). Now
> when users try to use a disabled specialization, they get more
> descriptive errors that mention __hash_not_enabled instead of
> __hash_enum.
>
> Additionally, adjust __hash_base to remove the deprecated result_type
> and argument_type typedefs for C++20 and later.
>
> In the current code we use a __poison_hash base class in the std::hash
> specializations for std::unique_ptr, std::optional, and std::variant.
> The primary template of __poison_hash has deleted special members, which
> is used to conditionally disable the derived std::hash specialization.
> This can also result in confusing diagnostics, because seeing "poison"
> in an enabled specialization is misleading. Only some uses of
> __poison_hash actually "poison" anything, i.e. cause a specialization to
> be disabled. In other cases it's just an empty base class that does
> nothing.
>
> This change removes __poison_hash and changes the std::hash
> specializations that were using it to conditionally derive from
> __hash_not_enabled instead. When the std::hash specialization is
> enabled, there is no more __poison_hash base class. However, to preserve
> the ABI properties of those std::hash specializations, we need to
> replace __poison_hash with some other empty base class. This is needed
> because in the current code std::hash<std::variant<int, const int>> has
> two __poison_hash<int> base classes, which must have unique addresses,
> so sizeof(std::hash<std::variant<int, const int>>) == 2. To preserve
> this unfortunate property, a new __hash_empty_base class is used as a
> base class to re-introduce du0plicate base classes that increase the
> class size. For the unstable ABI we don't use __hash_empty_base so the
> std::hash<std::variant<T...>> specializations are always size 1, and
> the class hierarchy is much simpler so will compile faster.
>
> Additionally, remove the result_type and argument_type typedefs from all
> disabled specializations of std::hash for std::unique_ptr,
> std::optional, and std::variant. Those typedefs are useless for disabled
> specializations, and although the standard doesn't say they must *not*
> be present for disabled specializations, it certainly only requires them
> for enabled specializations. Finally, for C++20 the typedefs are also
> removed from enabled specializations of std::hash for std::unique_ptr,
> std::optional, and std::variant.
>
> libstdc++-v3/ChangeLog:
>
>         * doc/xml/manual/evolution.xml: Document removal of nested types
>         from std::hash specializations.
>         * doc/html/manual/api.html: Regenerate.
>         * include/bits/functional_hash.h (__hash_base): Remove
>         deprecated nested types for C++20.
>         (__hash_empty_base): Define new class template.
>         (__is_hash_enabled_for): Define new variable template.
>         (__poison_hash): Remove.
>         (__hash_not_enabled): Define new class template.
>         (__hash_enum): Remove partial specialization for non-enums.
>         (hash): Derive from __hash_not_enabled for non-enums, instead of
>         __hash_enum.
>         * include/bits/unique_ptr.h (__uniq_ptr_hash): Derive from
>         __hash_base. Conditionally derive from __hash_empty_base.
>         (__uniq_ptr_hash<>): Remove disabled specialization.
>         (hash): Do not derive from __hash_base unconditionally.
>         Conditionally derive from either __uniq_ptr_hash or
>         __hash_not_enabled.
>         * include/std/optional (__optional_hash_call_base): Remove.
>         (__optional_hash): Define new class template.
>         (hash): Derive from either
>         (hash): Conditionally derive from either __optional_hash or
>         __hash_not_enabled. Remove nested typedefs.
>         * include/std/variant (_Base_dedup): Replace __poison_hash with
>         __hash_empty_base.
>         (__variant_hash_call_base_impl): Remove.
>         (__variant_hash): Define new class template.
>         (hash): Conditionally derive from either __variant_hash or
>         __hash_not_enabled. Remove nested typedefs.
>         * testsuite/20_util/optional/hash.cc: Check whether nested types
>         are present.
>         * testsuite/20_util/variant/hash.cc: Likewise.
>         * testsuite/20_util/optional/hash_abi.cc: New test.
>         * testsuite/20_util/unique_ptr/hash/abi.cc: New test.
>         * testsuite/20_util/unique_ptr/hash/types.cc: New test.
>         * testsuite/20_util/variant/hash_abi.cc: New test.
> ---
> Tested x86_64-linux.
>
> Also available for review at:
> https://forge.sourceware.org/gcc/gcc-TEST/pulls/16
>
>  libstdc++-v3/doc/html/manual/api.html         |  3 +
>  libstdc++-v3/doc/xml/manual/evolution.xml     |  5 ++
>  libstdc++-v3/include/bits/functional_hash.h   | 47 +++++++-------
>  libstdc++-v3/include/bits/unique_ptr.h        | 18 +++---
>  libstdc++-v3/include/std/optional             | 26 ++++----
>  libstdc++-v3/include/std/variant              | 41 ++++++------
>  .../testsuite/20_util/optional/hash.cc        | 33 ++++++++++
>  .../testsuite/20_util/optional/hash_abi.cc    | 35 ++++++++++
>  .../testsuite/20_util/unique_ptr/hash/abi.cc  | 64 +++++++++++++++++++
>  .../20_util/unique_ptr/hash/types.cc          | 53 +++++++++++++++
>  .../testsuite/20_util/variant/hash.cc         | 34 ++++++++++
>  .../testsuite/20_util/variant/hash_abi.cc     | 48 ++++++++++++++
>  12 files changed, 344 insertions(+), 63 deletions(-)
>  create mode 100644 libstdc++-v3/testsuite/20_util/optional/hash_abi.cc
>  create mode 100644 libstdc++-v3/testsuite/20_util/unique_ptr/hash/abi.cc
>  create mode 100644 libstdc++-v3/testsuite/20_util/unique_ptr/hash/types.cc
>  create mode 100644 libstdc++-v3/testsuite/20_util/variant/hash_abi.cc
>
> diff --git a/libstdc++-v3/doc/html/manual/api.html 
> b/libstdc++-v3/doc/html/manual/api.html
> index 2ccfc07b83e..7a4c7d9fe62 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">&lt;cstdalign&gt;</code>,
>  <code class="filename">&lt;cstdbool&gt;</code>, and
>  <code class="filename">&lt;ctgmath&gt;</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 6b134de0e71..84f8ab9d298 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">&lt;ctgmath&gt;</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 e7d8c6c2054..6815edeb833 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 182173aa857..cf24ba80a61 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 2e663d13f86..b8eedeec781 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 bd0f9c3252a..32e53998083 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 e441c87e0c9..607f56459ab 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 00000000000..78e992c4e41
> --- /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 00000000000..9c0a32c49c9
> --- /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 00000000000..dbe3d8084fe
> --- /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 f3ab4e0d9ca..52fc759fc34 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 00000000000..19cf72b54de
> --- /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> );
> --
> 2.47.0
>


Reply via email to