{"id":2226220,"url":"http://patchwork.ozlabs.org/api/patches/2226220/?format=json","web_url":"http://patchwork.ozlabs.org/project/gcc/patch/bmm.hhub9plyek.gcc.gcc-TEST.redi.16.1.1@forge-stage.sourceware.org/","project":{"id":17,"url":"http://patchwork.ozlabs.org/api/projects/17/?format=json","name":"GNU Compiler Collection","link_name":"gcc","list_id":"gcc-patches.gcc.gnu.org","list_email":"gcc-patches@gcc.gnu.org","web_url":null,"scm_url":null,"webscm_url":null,"list_archive_url":"","list_archive_url_format":"","commit_url_format":""},"msgid":"<bmm.hhub9plyek.gcc.gcc-TEST.redi.16.1.1@forge-stage.sourceware.org>","list_archive_url":null,"date":"2026-04-22T10:26:26","name":"[v1,1/1] libstdc++: Refactor std::hash specializations","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"6f03f6a830cb95b96bfa80b8cea5f478257cb71e","submitter":{"id":93210,"url":"http://patchwork.ozlabs.org/api/people/93210/?format=json","name":"Jonathan Wakely via Sourceware Forge","email":"forge-bot+redi@forge-stage.sourceware.org"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/gcc/patch/bmm.hhub9plyek.gcc.gcc-TEST.redi.16.1.1@forge-stage.sourceware.org/mbox/","series":[{"id":500974,"url":"http://patchwork.ozlabs.org/api/series/500974/?format=json","web_url":"http://patchwork.ozlabs.org/project/gcc/list/?series=500974","date":"2026-04-22T10:26:26","name":"libstdc++: Refactor std::hash specializations","version":1,"mbox":"http://patchwork.ozlabs.org/series/500974/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2226220/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2226220/checks/","tags":{},"related":[],"headers":{"Return-Path":"<gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org>","X-Original-To":["incoming@patchwork.ozlabs.org","gcc-patches@gcc.gnu.org"],"Delivered-To":["patchwork-incoming@legolas.ozlabs.org","gcc-patches@gcc.gnu.org"],"Authentication-Results":["legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org\n (client-ip=2620:52:6:3111::32; helo=vm01.sourceware.org;\n envelope-from=gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org;\n receiver=patchwork.ozlabs.org)","sourceware.org; dmarc=none (p=none dis=none)\n header.from=forge-stage.sourceware.org","sourceware.org;\n spf=pass smtp.mailfrom=forge-stage.sourceware.org","server2.sourceware.org;\n arc=none smtp.remote-ip=38.145.34.39"],"Received":["from vm01.sourceware.org (vm01.sourceware.org\n [IPv6:2620:52:6:3111::32])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g0wml44WJz1yCv\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 22 Apr 2026 20:43:19 +1000 (AEST)","from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id 360764BB3BFE\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 22 Apr 2026 10:43:17 +0000 (GMT)","from forge-stage.sourceware.org (vm08.sourceware.org [38.145.34.39])\n by sourceware.org (Postfix) with ESMTPS id 47D2C4B92097\n for <gcc-patches@gcc.gnu.org>; Wed, 22 Apr 2026 10:27:19 +0000 (GMT)","from forge-stage.sourceware.org (localhost [IPv6:::1])\n (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256)\n (No client certificate requested)\n by forge-stage.sourceware.org (Postfix) with ESMTPS id 1EB8B405AA\n for <gcc-patches@gcc.gnu.org>; Wed, 22 Apr 2026 10:27:19 +0000 (UTC)"],"DKIM-Filter":["OpenDKIM Filter v2.11.0 sourceware.org 360764BB3BFE","OpenDKIM Filter v2.11.0 sourceware.org 47D2C4B92097"],"DMARC-Filter":"OpenDMARC Filter v1.4.2 sourceware.org 47D2C4B92097","ARC-Filter":"OpenARC Filter v1.0.0 sourceware.org 47D2C4B92097","ARC-Seal":"i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1776853639; cv=none;\n b=S/ruhXbRpc97d63m6GSK7MeKd8pUfYGjLyF1Cqdki5h9c4eHleBdJAmTchYyqeFWtxGB5hZYs7+6I+SnElqgS8HSOBCL+ToFWSav1XvgdqOcTgNLCnIS9BQLADyR61HBZugA2G0gI9WRnP0NYlgwAEsODzzdbJ60ApOwIn+xU+Q=","ARC-Message-Signature":"i=1; a=rsa-sha256; d=sourceware.org; s=key;\n t=1776853639; c=relaxed/simple;\n bh=0vhC0Fm3QRtfi347K8aonF4Bj+HFF8JxLe45NATaksc=;\n h=From:Date:Subject:To:Message-ID;\n b=vUUgHDDgwPlwwxJ5M2OhMDwto2mePV/KO+K4t950LyZLCLPaMlosBwaa2/CbP811IN2LKjvI2LWHECxKmYa/nkgQbMIVc2MNLPLsw688l71RDstR3BQLKzsUT48Xg0On5gsm0ZigvaLpMP+jk2/EeSJylsh0BepQVpCF7ZAX6zQ=","ARC-Authentication-Results":"i=1; server2.sourceware.org","From":"Jonathan Wakely via Sourceware Forge\n <forge-bot+redi@forge-stage.sourceware.org>","Date":"Wed, 22 Apr 2026 10:26:26 +0000","Subject":"[PATCH v1 1/1] libstdc++: Refactor std::hash specializations","To":"gcc-patches mailing list <gcc-patches@gcc.gnu.org>","Message-ID":"\n <bmm.hhub9plyek.gcc.gcc-TEST.redi.16.1.1@forge-stage.sourceware.org>","X-Mailer":"batrachomyomachia","X-Pull-Request-Organization":"gcc","X-Pull-Request-Repository":"gcc-TEST","X-Pull-Request":"https://forge.sourceware.org/gcc/gcc-TEST/pulls/16","References":"\n <bmm.hhub9plyek.gcc.gcc-TEST.redi.16.1.0@forge-stage.sourceware.org>","In-Reply-To":"\n <bmm.hhub9plyek.gcc.gcc-TEST.redi.16.1.0@forge-stage.sourceware.org>","X-Patch-URL":"\n https://forge.sourceware.org/redi/gcc/commit/34be1a8927df3aad5db13394a813c60dd83c4e14","X-BeenThere":"gcc-patches@gcc.gnu.org","X-Mailman-Version":"2.1.30","Precedence":"list","List-Id":"Gcc-patches mailing list <gcc-patches.gcc.gnu.org>","List-Unsubscribe":"<https://gcc.gnu.org/mailman/options/gcc-patches>,\n <mailto:gcc-patches-request@gcc.gnu.org?subject=unsubscribe>","List-Archive":"<https://gcc.gnu.org/pipermail/gcc-patches/>","List-Post":"<mailto:gcc-patches@gcc.gnu.org>","List-Help":"<mailto:gcc-patches-request@gcc.gnu.org?subject=help>","List-Subscribe":"<https://gcc.gnu.org/mailman/listinfo/gcc-patches>,\n <mailto:gcc-patches-request@gcc.gnu.org?subject=subscribe>","Reply-To":"gcc-patches mailing list <gcc-patches@gcc.gnu.org>, redi@gcc.gnu.org","Errors-To":"gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org"},"content":"From: Jonathan Wakely <jwakely@redhat.com>\n\nThis attempts to simplify and clean up our std::hash code. The primary\nbenefit is improved diagnostics for users when they do something wrong\ninvolving std::hash or unordered containers. An additional benefit is\nthat for the unstable ABI (--enable-symvers=gnu-versioned-namespace) we\ncan reduce the memory footprint of several std::hash specializations.\n\nIn the current design, __hash_enum is a base class of the std::hash\nprimary template, but the partial specialization of __hash_enum for\nnon-enum types is disabled.  This means that if a user forgets to\nspecialize std::hash for their class type (or forgets to use a custom\nhash function for unordered containers) they get error messages about\nstd::__hash_enum not being constructible.  This is confusing when there\nis no enum type involved: why should users care about __hash_enum not\nbeing constructible if they're not trying to hash enums?\n\nThis change makes the std::hash primary template only derive from\n__hash_enum when the template argument type is an enum. Otherwise, it\nderives directly from a new class template, __hash_not_enabled. This new\nclass template defines the deleted members that cause a given std::hash\nspecialization to be a disabled specialization (as per P0513R0). Now\nwhen users try to use a disabled specialization, they get more\ndescriptive errors that mention __hash_not_enabled instead of\n__hash_enum.\n\nAdditionally, adjust __hash_base to remove the deprecated result_type\nand argument_type typedefs for C++20 and later.\n\nIn the current code we use a __poison_hash base class in the std::hash\nspecializations for std::unique_ptr, std::optional, and std::variant.\nThe primary template of __poison_hash has deleted special members, which\nis used to conditionally disable the derived std::hash specialization.\nThis can also result in confusing diagnostics, because seeing \"poison\"\nin an enabled specialization is misleading. Only some uses of\n__poison_hash actually \"poison\" anything, i.e. cause a specialization to\nbe disabled. In other cases it's just an empty base class that does\nnothing.\n\nThis change removes __poison_hash and changes the std::hash\nspecializations that were using it to conditionally derive from\n__hash_not_enabled instead. When the std::hash specialization is\nenabled, there is no more __poison_hash base class. However, to preserve\nthe ABI properties of those std::hash specializations, we need to\nreplace __poison_hash with some other empty base class. This is needed\nbecause in the current code std::hash<std::variant<int, const int>> has\ntwo __poison_hash<int> base classes, which must have unique addresses,\nso sizeof(std::hash<std::variant<int, const int>>) == 2. To preserve\nthis unfortunate property, a new __hash_empty_base class is used as a\nbase class to re-introduce du0plicate base classes that increase the\nclass size. For the unstable ABI we don't use __hash_empty_base so the\nstd::hash<std::variant<T...>> specializations are always size 1, and\nthe class hierarchy is much simpler so will compile faster.\n\nAdditionally, remove the result_type and argument_type typedefs from all\ndisabled specializations of std::hash for std::unique_ptr,\nstd::optional, and std::variant. Those typedefs are useless for disabled\nspecializations, and although the standard doesn't say they must *not*\nbe present for disabled specializations, it certainly only requires them\nfor enabled specializations. Finally, for C++20 the typedefs are also\nremoved from enabled specializations of std::hash for std::unique_ptr,\nstd::optional, and std::variant.\n\nlibstdc++-v3/ChangeLog:\n\n\t* doc/xml/manual/evolution.xml: Document removal of nested types\n\tfrom std::hash specializations.\n\t* doc/html/manual/api.html: Regenerate.\n\t* include/bits/functional_hash.h (__hash_base): Remove\n\tdeprecated nested types for C++20.\n\t(__hash_empty_base): Define new class template.\n\t(__is_hash_enabled_for): Define new variable template.\n\t(__poison_hash): Remove.\n\t(__hash_not_enabled): Define new class template.\n\t(__hash_enum): Remove partial specialization for non-enums.\n\t(hash): Derive from __hash_not_enabled for non-enums, instead of\n\t__hash_enum.\n\t* include/bits/unique_ptr.h (__uniq_ptr_hash): Derive from\n\t__hash_base. Conditionally derive from __hash_empty_base.\n\t(__uniq_ptr_hash<>): Remove disabled specialization.\n\t(hash): Do not derive from __hash_base unconditionally.\n\tConditionally derive from either __uniq_ptr_hash or\n\t__hash_not_enabled.\n\t* include/std/optional (__optional_hash_call_base): Remove.\n\t(__optional_hash): Define new class template.\n\t(hash): Derive from either\n\t(hash): Conditionally derive from either __optional_hash or\n\t__hash_not_enabled. Remove nested typedefs.\n\t* include/std/variant (_Base_dedup): Replace __poison_hash with\n\t__hash_empty_base.\n\t(__variant_hash_call_base_impl): Remove.\n\t(__variant_hash): Define new class template.\n\t(hash): Conditionally derive from either __variant_hash or\n\t__hash_not_enabled. Remove nested typedefs.\n\t* testsuite/20_util/optional/hash.cc: Check whether nested types\n\tare present.\n\t* testsuite/20_util/variant/hash.cc: Likewise.\n\t* testsuite/20_util/optional/hash_abi.cc: New test.\n\t* testsuite/20_util/unique_ptr/hash/abi.cc: New test.\n\t* testsuite/20_util/unique_ptr/hash/types.cc: New test.\n\t* testsuite/20_util/variant/hash_abi.cc: New test.\n---\n libstdc++-v3/doc/html/manual/api.html         |  3 +\n libstdc++-v3/doc/xml/manual/evolution.xml     |  5 ++\n libstdc++-v3/include/bits/functional_hash.h   | 47 +++++++-------\n libstdc++-v3/include/bits/unique_ptr.h        | 18 +++---\n libstdc++-v3/include/std/optional             | 26 ++++----\n libstdc++-v3/include/std/variant              | 41 ++++++------\n .../testsuite/20_util/optional/hash.cc        | 33 ++++++++++\n .../testsuite/20_util/optional/hash_abi.cc    | 35 ++++++++++\n .../testsuite/20_util/unique_ptr/hash/abi.cc  | 64 +++++++++++++++++++\n .../20_util/unique_ptr/hash/types.cc          | 53 +++++++++++++++\n .../testsuite/20_util/variant/hash.cc         | 34 ++++++++++\n .../testsuite/20_util/variant/hash_abi.cc     | 48 ++++++++++++++\n 12 files changed, 344 insertions(+), 63 deletions(-)\n create mode 100644 libstdc++-v3/testsuite/20_util/optional/hash_abi.cc\n create mode 100644 libstdc++-v3/testsuite/20_util/unique_ptr/hash/abi.cc\n create mode 100644 libstdc++-v3/testsuite/20_util/unique_ptr/hash/types.cc\n create mode 100644 libstdc++-v3/testsuite/20_util/variant/hash_abi.cc","diff":"diff --git a/libstdc++-v3/doc/html/manual/api.html b/libstdc++-v3/doc/html/manual/api.html\nindex 2ccfc07b83e5..7a4c7d9fe620 100644\n--- a/libstdc++-v3/doc/html/manual/api.html\n+++ b/libstdc++-v3/doc/html/manual/api.html\n@@ -506,4 +506,7 @@ and removed in C++20:\n <code class=\"filename\">&lt;cstdalign&gt;</code>,\n <code class=\"filename\">&lt;cstdbool&gt;</code>, and\n <code class=\"filename\">&lt;ctgmath&gt;</code>.\n+</p><p>\n+Nested <code class=\"code\">result_type</code> and <code class=\"code\">argument_type</code> removed from\n+<code class=\"classname\">std::hash</code> specializations for C++20.\n </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>\n\\ No newline at end of file\ndiff --git a/libstdc++-v3/doc/xml/manual/evolution.xml b/libstdc++-v3/doc/xml/manual/evolution.xml\nindex 6b134de0e71f..84f8ab9d2981 100644\n--- a/libstdc++-v3/doc/xml/manual/evolution.xml\n+++ b/libstdc++-v3/doc/xml/manual/evolution.xml\n@@ -1145,6 +1145,11 @@ and removed in C++20:\n <filename class=\"headerfile\">&lt;ctgmath&gt;</filename>.\n </para>\n \n+<para>\n+Nested <code>result_type</code> and <code>argument_type</code> removed from\n+<classname>std::hash</classname> specializations for C++20.\n+</para>\n+\n </section>\n \n </section>\ndiff --git a/libstdc++-v3/include/bits/functional_hash.h b/libstdc++-v3/include/bits/functional_hash.h\nindex e7d8c6c20545..6815edeb8333 100644\n--- a/libstdc++-v3/include/bits/functional_hash.h\n+++ b/libstdc++-v3/include/bits/functional_hash.h\n@@ -52,43 +52,44 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION\n   template<typename _Result, typename _Arg>\n     struct __hash_base\n     {\n+#if __cplusplus < 202002L\n       typedef _Result     result_type _GLIBCXX17_DEPRECATED;\n       typedef _Arg      argument_type _GLIBCXX17_DEPRECATED;\n+#endif\n     };\n \n+#if ! _GLIBCXX_INLINE_VERSION\n+  // Some std::hash specializations inherit this for ABI compatibility reasons.\n+  template<typename _Tp> struct __hash_empty_base { };\n+#endif\n+\n   /// Primary class template hash.\n   template<typename _Tp>\n     struct hash;\n \n+#pragma GCC diagnostic push\n+#pragma GCC diagnostic ignored \"-Wc++14-extensions\"\n   template<typename _Tp, typename = void>\n-    struct __poison_hash\n-    {\n-      static constexpr bool __enable_hash_call = false;\n-    private:\n-      // Private rather than deleted to be non-trivially-copyable.\n-      __poison_hash(__poison_hash&&);\n-      ~__poison_hash();\n-    };\n+    constexpr bool __is_hash_enabled_for = false;\n \n   template<typename _Tp>\n-    struct __poison_hash<_Tp, __void_t<decltype(hash<_Tp>()(declval<_Tp>()))>>\n-    {\n-      static constexpr bool __enable_hash_call = true;\n-    };\n+    constexpr bool\n+    __is_hash_enabled_for<_Tp,\n+\t\t\t  __void_t<decltype(hash<_Tp>()(declval<_Tp>()))>>\n+      = true;\n+#pragma GCC diagnostic pop\n \n-  // Helper struct for SFINAE-poisoning non-enum types.\n-  template<typename _Tp, bool = is_enum<_Tp>::value>\n-    struct __hash_enum\n+  // Helper struct for defining disabled specializations of std::hash.\n+  template<typename _Tp>\n+    struct __hash_not_enabled\n     {\n-    private:\n-      // Private rather than deleted to be non-trivially-copyable.\n-      __hash_enum(__hash_enum&&);\n-      ~__hash_enum();\n+      __hash_not_enabled(__hash_not_enabled&&) = delete;\n+      ~__hash_not_enabled() = delete;\n     };\n \n   // Helper struct for hash with enum types.\n-  template<typename _Tp>\n-    struct __hash_enum<_Tp, true> : public __hash_base<size_t, _Tp>\n+  template<typename _Tp, bool = true>\n+    struct __hash_enum : public __hash_base<size_t, _Tp>\n     {\n       size_t\n       operator()(_Tp __val) const noexcept\n@@ -99,9 +100,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION\n     };\n \n   /// Primary class template hash, usable for enum types only.\n-  // Use with non-enum types still SFINAES.\n   template<typename _Tp>\n-    struct hash : __hash_enum<_Tp>\n+    struct hash\n+    : __conditional_t<__is_enum(_Tp), __hash_enum<_Tp>, __hash_not_enabled<_Tp>>\n     { };\n \n   /// Partial specializations for pointer types.\ndiff --git a/libstdc++-v3/include/bits/unique_ptr.h b/libstdc++-v3/include/bits/unique_ptr.h\nindex 182173aa8571..cf24ba80a61e 100644\n--- a/libstdc++-v3/include/bits/unique_ptr.h\n+++ b/libstdc++-v3/include/bits/unique_ptr.h\n@@ -1012,11 +1012,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION\n   /// @} relates unique_ptr\n \n   /// @cond undocumented\n-  template<typename _Up, typename _Ptr = typename _Up::pointer,\n-\t   bool = __poison_hash<_Ptr>::__enable_hash_call>\n+  template<typename _Up, typename _Ptr = typename _Up::pointer>\n     struct __uniq_ptr_hash\n+    : public __hash_base<size_t, _Up>\n #if ! _GLIBCXX_INLINE_VERSION\n-    : private __poison_hash<_Ptr>\n+    , private __hash_empty_base<_Ptr>\n #endif\n     {\n       size_t\n@@ -1025,17 +1025,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION\n       { return hash<_Ptr>()(__u.get()); }\n     };\n \n-  template<typename _Up, typename _Ptr>\n-    struct __uniq_ptr_hash<_Up, _Ptr, false>\n-    : private __poison_hash<_Ptr>\n-    { };\n+  template<typename _Up>\n+    using __uniq_ptr_hash_base\n+      = __conditional_t<__is_hash_enabled_for<typename _Up::pointer>,\n+\t\t\t     __uniq_ptr_hash<_Up>,\n+\t\t\t     __hash_not_enabled<typename _Up::pointer>>;\n   /// @endcond\n \n   /// std::hash specialization for unique_ptr.\n   template<typename _Tp, typename _Dp>\n     struct hash<unique_ptr<_Tp, _Dp>>\n-    : public __hash_base<size_t, unique_ptr<_Tp, _Dp>>,\n-      public __uniq_ptr_hash<unique_ptr<_Tp, _Dp>>\n+    : public __uniq_ptr_hash_base<unique_ptr<_Tp, _Dp>>\n     { };\n \n #ifdef __glibcxx_make_unique // C++ >= 14 && HOSTED\ndiff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional\nindex 2e663d13f86a..b8eedeec7817 100644\n--- a/libstdc++-v3/include/std/optional\n+++ b/libstdc++-v3/include/std/optional\n@@ -1732,10 +1732,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION\n \n   // Hash.\n \n-  template<typename _Tp, typename _Up = remove_const_t<_Tp>,\n-\t   bool = __poison_hash<_Up>::__enable_hash_call>\n-    struct __optional_hash_call_base\n+  template<typename _Tp, typename _Up = remove_const_t<_Tp>>\n+    struct __optional_hash\n+#if ! _GLIBCXX_INLINE_VERSION\n+    : public __hash_empty_base<_Up>\n+#endif\n     {\n+#if __cplusplus < 202002L\n+      using result_type [[__deprecated__]] = size_t;\n+      using argument_type [[__deprecated__]] = optional<_Tp>;\n+#endif\n+\n       size_t\n       operator()(const optional<_Tp>& __t) const\n       noexcept(noexcept(hash<_Up>{}(*__t)))\n@@ -1747,17 +1754,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION\n       }\n     };\n \n-  template<typename _Tp, typename _Up>\n-    struct __optional_hash_call_base<_Tp, _Up, false> {};\n-\n   template<typename _Tp>\n     struct hash<optional<_Tp>>\n-    : private __poison_hash<remove_const_t<_Tp>>,\n-      public __optional_hash_call_base<_Tp>\n-    {\n-      using result_type [[__deprecated__]] = size_t;\n-      using argument_type [[__deprecated__]] = optional<_Tp>;\n-    };\n+    : public __conditional_t<__is_hash_enabled_for<remove_const_t<_Tp>>,\n+\t\t\t     __optional_hash<_Tp>,\n+\t\t\t     __hash_not_enabled<_Tp>>\n+    { };\n \n   template<typename _Tp>\n     struct __is_fast_hash<hash<optional<_Tp>>> : __is_fast_hash<hash<_Tp>>\ndiff --git a/libstdc++-v3/include/std/variant b/libstdc++-v3/include/std/variant\nindex bd0f9c3252a5..32e539980839 100644\n--- a/libstdc++-v3/include/std/variant\n+++ b/libstdc++-v3/include/std/variant\n@@ -1094,7 +1094,8 @@ namespace __variant\n \t= __gen_vtable_impl<_Array_type, std::index_sequence<>>::_S_apply();\n     };\n \n-  template<size_t _Np, typename _Tp>\n+#if ! _GLIBCXX_INLINE_VERSION\n+  template<size_t _Nm, typename _Tp>\n     struct _Base_dedup : public _Tp { };\n \n   template<typename _Variant, typename __indices>\n@@ -1103,7 +1104,9 @@ namespace __variant\n   template<typename... _Types, size_t... __indices>\n     struct _Variant_hash_base<variant<_Types...>,\n \t\t\t      std::index_sequence<__indices...>>\n-    : _Base_dedup<__indices, __poison_hash<remove_const_t<_Types>>>... { };\n+    : _Base_dedup<__indices, __hash_empty_base<remove_const_t<_Types>>>...\n+    { };\n+#endif\n \n   // Equivalent to decltype(get<_Np>(as-variant(declval<_Variant>())))\n   template<size_t _Np, typename _Variant,\n@@ -1987,9 +1990,14 @@ namespace __detail::__variant\n #endif\n \n   /// @cond undocumented\n-  template<bool, typename... _Types>\n-    struct __variant_hash_call_base_impl\n+  template<typename... _Types>\n+    struct __variant_hash\n     {\n+#if __cplusplus < 202002L\n+      using result_type [[__deprecated__]] = size_t;\n+      using argument_type [[__deprecated__]] = variant<_Types...>;\n+#endif\n+\n       size_t\n       operator()(const variant<_Types...>& __t) const\n       noexcept((is_nothrow_invocable_v<hash<decay_t<_Types>>, _Types> && ...))\n@@ -2009,31 +2017,26 @@ namespace __detail::__variant\n \treturn __ret;\n       }\n     };\n-\n-  template<typename... _Types>\n-    struct __variant_hash_call_base_impl<false, _Types...> {};\n-\n-  template<typename... _Types>\n-    using __variant_hash_call_base =\n-    __variant_hash_call_base_impl<(__poison_hash<remove_const_t<_Types>>::\n-\t\t\t\t   __enable_hash_call &&...), _Types...>;\n   /// @endcond\n \n   template<typename... _Types>\n     struct hash<variant<_Types...>>\n-    : private __detail::__variant::_Variant_hash_base<\n-\tvariant<_Types...>, std::index_sequence_for<_Types...>>,\n-      public __variant_hash_call_base<_Types...>\n-    {\n-      using result_type [[__deprecated__]] = size_t;\n-      using argument_type [[__deprecated__]] = variant<_Types...>;\n-    };\n+    : __conditional_t<(__is_hash_enabled_for<remove_const_t<_Types>> && ...),\n+\t\t      __variant_hash<_Types...>,\n+\t\t      __hash_not_enabled<variant<_Types...>>>\n+#if ! _GLIBCXX_INLINE_VERSION\n+      , __detail::__variant::_Variant_hash_base<variant<_Types...>,\n+\t\t\t\t\t\tindex_sequence_for<_Types...>>\n+#endif\n+    { };\n \n   template<>\n     struct hash<monostate>\n     {\n+#if __cplusplus < 202002L\n       using result_type [[__deprecated__]] = size_t;\n       using argument_type [[__deprecated__]] = monostate;\n+#endif\n \n       size_t\n       operator()(const monostate&) const noexcept\ndiff --git a/libstdc++-v3/testsuite/20_util/optional/hash.cc b/libstdc++-v3/testsuite/20_util/optional/hash.cc\nindex e441c87e0c95..607f56459aba 100644\n--- a/libstdc++-v3/testsuite/20_util/optional/hash.cc\n+++ b/libstdc++-v3/testsuite/20_util/optional/hash.cc\n@@ -49,3 +49,36 @@ int main()\n   std::optional<const int> x3 = x2;\n   VERIFY(std::hash<int>()(x) == std::hash<std::optional<const int>>()(x3));\n }\n+\n+// Check for presence/absence of nested types.\n+\n+template<typename T> using res_type = typename std::hash<T>::result_type;\n+template<typename T> using arg_type = typename std::hash<T>::argument_type;\n+\n+template<typename Opt, typename = void>\n+constexpr bool has_res_type = false;\n+template<typename Opt>\n+constexpr bool has_res_type<Opt, std::void_t<res_type<Opt>>> = true;\n+template<typename Opt, typename = void>\n+constexpr bool has_arg_type = false;\n+template<typename Opt>\n+constexpr bool has_arg_type<Opt, std::void_t<arg_type<Opt>>> = true;\n+\n+template<typename T>\n+constexpr bool has_no_types\n+  = ! has_res_type<std::optional<T>> && ! has_arg_type<std::optional<T>>;\n+\n+#if __cplusplus >= 202002L\n+// Nested types result_type and argument_type are not present in C++20\n+static_assert( has_no_types<int> );\n+static_assert( has_no_types<double> );\n+#else\n+// Nested types result_type and argument_type are deprecated in C++17.\n+using R1 = std::hash<std::optional<int>>::result_type; // { dg-warning \"deprecated\" \"\" { target c++17_only } }\n+using A1 = std::hash<std::optional<int>>::argument_type; // { dg-warning \"deprecated\" \"\" { target c++17_only } }\n+using R2 = std::hash<std::optional<char>>::result_type; // { dg-warning \"deprecated\" \"\" { target c++17_only } }\n+using A2 = std::hash<std::optional<char>>::argument_type; // { dg-warning \"deprecated\" \"\" { target c++17_only } }\n+#endif\n+\n+// Disabled specializations do not have the nested types.\n+static_assert( has_no_types<S> );\ndiff --git a/libstdc++-v3/testsuite/20_util/optional/hash_abi.cc b/libstdc++-v3/testsuite/20_util/optional/hash_abi.cc\nnew file mode 100644\nindex 000000000000..78e992c4e41d\n--- /dev/null\n+++ b/libstdc++-v3/testsuite/20_util/optional/hash_abi.cc\n@@ -0,0 +1,35 @@\n+// { dg-do compile { target c++17 } }\n+\n+#include <optional>\n+\n+struct S { }; // std::hash<S> is a disabled specialization.\n+\n+template<typename T>\n+constexpr std::size_t hash_size = sizeof(std::hash<std::optional<T>>);\n+\n+template<typename... Ts>\n+struct MultiHash : std::hash<std::optional<Ts>>...\n+{ };\n+\n+#if _GLIBCXX_INLINE_VERSION\n+// For the unstable ABI the size should always be one.\n+template<std::size_t Size, typename... Ts>\n+constexpr bool check_hash_size = sizeof(MultiHash<Ts...>) == 1;\n+#else\n+// For the default ABI, each std::hash<std::optional<T>> specialization has\n+// a base class of type __hash_empty_base<remove_cv_t<T>> and if\n+// the same type occurs more than once they must have unique addresses.\n+template<std::size_t Size, typename... Ts>\n+constexpr bool check_hash_size = sizeof(MultiHash<Ts...>) == Size;\n+#endif\n+\n+static_assert( check_hash_size<1, int> );\n+static_assert( check_hash_size<1, int, long, double> );\n+static_assert( check_hash_size<2, int, const int> );\n+static_assert( check_hash_size<2, int, long, const int> );\n+\n+static_assert( check_hash_size<1, S> );\n+static_assert( check_hash_size<1, int, S> );\n+static_assert( check_hash_size<2, int, S, const int> );\n+static_assert( check_hash_size<2, int, S, const int, const S> );\n+static_assert( check_hash_size<2, int, S, const S, const int> );\ndiff --git a/libstdc++-v3/testsuite/20_util/unique_ptr/hash/abi.cc b/libstdc++-v3/testsuite/20_util/unique_ptr/hash/abi.cc\nnew file mode 100644\nindex 000000000000..9c0a32c49c92\n--- /dev/null\n+++ b/libstdc++-v3/testsuite/20_util/unique_ptr/hash/abi.cc\n@@ -0,0 +1,64 @@\n+// { dg-do compile { target c++17 } }\n+\n+#include <memory>\n+\n+struct S { }; // std::hash<S> is a disabled specialization.\n+\n+template<typename T, typename D = std::default_delete<T>>\n+constexpr std::size_t hash_size = sizeof(std::hash<std::unique_ptr<T, D>>);\n+\n+template<typename... Ts>\n+struct MultiHash : std::hash<std::unique_ptr<Ts>>...\n+{ };\n+\n+#if _GLIBCXX_INLINE_VERSION\n+// For the unstable ABI the size should always be one.\n+template<std::size_t Size, typename... Ts>\n+constexpr bool check_hash_size = sizeof(MultiHash<Ts...>) == 1;\n+#else\n+// For the default ABI, each std::hash<std::unique_ptr<T,D >> specialization\n+// has a base class of type __hash_empty_base<D::pointer> and if\n+// the same type occurs more than once they must have unique addresses.\n+template<std::size_t Size, typename... Ts>\n+constexpr bool check_hash_size = sizeof(MultiHash<Ts...>) == Size;\n+#endif\n+\n+// All these types have distinct D::pointer types, so no duplicate base classes\n+static_assert( check_hash_size<1, int>, \"\" );\n+static_assert( check_hash_size<1, int, long, double>, \"\" );\n+static_assert( check_hash_size<1, int, const int>, \"\" );\n+static_assert( check_hash_size<1, int, long, const int>, \"\" );\n+// Likewise for these disabled specializations:\n+static_assert( check_hash_size<1, S>, \"\" );\n+static_assert( check_hash_size<1, int, S>, \"\" );\n+static_assert( check_hash_size<1, int, S, const int>, \"\" );\n+static_assert( check_hash_size<1, int, S, const int, const S>, \"\" );\n+static_assert( check_hash_size<1, int, S, const S, const int>, \"\" );\n+\n+// But this has two base classes of type __hash_empty_base<int*>:\n+static_assert( check_hash_size<2, int, int[]>, \"\" );\n+static_assert( check_hash_size<2, int, int[], const int>, \"\" );\n+// And this has two base classes of type __hash_not_enabled<S*>:\n+static_assert( check_hash_size<2, S, S[]>, \"\" );\n+static_assert( check_hash_size<2, S, S[], const S>, \"\" );\n+\n+struct Del : std::default_delete<int> { };\n+using P = std::unique_ptr<int>;\n+using PD = std::unique_ptr<int, Del>;\n+using PC = std::unique_ptr<int, std::default_delete<const int>>;\n+using PA = std::unique_ptr<int[]>;\n+struct HashClash\n+: std::hash<P>, std::hash<PD>, std::hash<PC>, std::hash<PA>\n+{ };\n+#if _GLIBCXX_INLINE_VERSION\n+static_assert(sizeof(HashClash) == 1, \"No duplicate bases for unstable ABI\");\n+#else\n+static_assert(sizeof(HashClash) == 4, \"four __hash_empty_base<int*> bases\");\n+#endif\n+\n+struct Del2 : std::default_delete<const int> { using pointer = const int*; };\n+using PD2 = std::unique_ptr<int, Del2>;\n+struct Hash2\n+: std::hash<PD>, std::hash<PD2>\n+{ };\n+static_assert(sizeof(Hash2) == 1, \"No duplicate bases\");\ndiff --git a/libstdc++-v3/testsuite/20_util/unique_ptr/hash/types.cc b/libstdc++-v3/testsuite/20_util/unique_ptr/hash/types.cc\nnew file mode 100644\nindex 000000000000..dbe3d8084fe8\n--- /dev/null\n+++ b/libstdc++-v3/testsuite/20_util/unique_ptr/hash/types.cc\n@@ -0,0 +1,53 @@\n+// { dg-do compile { target c++11 } }\n+\n+#include <memory>\n+\n+\n+// Check for presence/absence of nested types.\n+\n+template<typename T> using res_type = typename std::hash<T>::result_type;\n+template<typename T> using arg_type = typename std::hash<T>::argument_type;\n+\n+template<typename UniqPtr, typename = void>\n+constexpr bool\n+has_res_type(...)\n+{ return false; }\n+\n+template<typename UniqPtr>\n+constexpr typename std::is_void<res_type<UniqPtr>>::value_type // i.e. bool\n+has_res_type()\n+{ return true; }\n+\n+template<typename UniqPtr, typename = void>\n+constexpr bool\n+has_arg_type(...)\n+{ return false; }\n+\n+template<typename UniqPtr>\n+constexpr typename std::is_void<arg_type<UniqPtr>>::value_type // i.e. bool\n+has_arg_type()\n+{ return true; }\n+\n+template<typename UniqPtr>\n+constexpr bool\n+has_no_types()\n+{ return ! has_res_type<UniqPtr>() && ! has_arg_type<UniqPtr>(); }\n+\n+#if __cplusplus >= 202002L\n+// Nested types result_type and argument_type are not present in C++20\n+static_assert( has_no_types<std::unique_ptr<int>>() );\n+static_assert( has_no_types<std::unique_ptr<double>>() );\n+#else\n+// Nested types result_type and argument_type are deprecated in C++17.\n+using R1 = std::hash<std::unique_ptr<int>>::result_type; // { dg-warning \"deprecated\" \"\" { target c++17_only } }\n+using A1 = std::hash<std::unique_ptr<int>>::argument_type; // { dg-warning \"deprecated\" \"\" { target c++17_only } }\n+#endif\n+\n+struct S { };\n+template<> struct std::hash<S*> // disabled specialization\n+{\n+  hash(hash&&) = delete;\n+  ~hash() = delete;\n+};\n+// Disabled specializations do not have the nested types.\n+static_assert( has_no_types<std::unique_ptr<S>>(), \"disabled specialization\" );\ndiff --git a/libstdc++-v3/testsuite/20_util/variant/hash.cc b/libstdc++-v3/testsuite/20_util/variant/hash.cc\nindex f3ab4e0d9cab..52fc759fc34e 100644\n--- a/libstdc++-v3/testsuite/20_util/variant/hash.cc\n+++ b/libstdc++-v3/testsuite/20_util/variant/hash.cc\n@@ -48,3 +48,37 @@ int main()\n   std::variant<int> x2 = 42;\n   VERIFY(std::hash<int>()(x) == std::hash<std::variant<int>>()(x2));\n }\n+\n+// Check for presence/absence of nested types.\n+\n+template<typename T> using res_type = typename std::hash<T>::result_type;\n+template<typename T> using arg_type = typename std::hash<T>::argument_type;\n+\n+template<typename Variant, typename = void>\n+constexpr bool has_res_type = false;\n+template<typename Variant>\n+constexpr bool has_res_type<Variant, std::void_t<res_type<Variant>>> = true;\n+template<typename Variant, typename = void>\n+constexpr bool has_arg_type = false;\n+template<typename Variant>\n+constexpr bool has_arg_type<Variant, std::void_t<arg_type<Variant>>> = true;\n+\n+template<typename... Ts>\n+constexpr bool has_no_types\n+  = ! has_res_type<std::variant<Ts...>> && ! has_arg_type<std::variant<Ts...>>;\n+\n+#if __cplusplus >= 202002L\n+// Nested types result_type and argument_type are not present in C++20\n+static_assert( has_no_types<int> );\n+static_assert( has_no_types<int, double> );\n+#else\n+// Nested types result_type and argument_type are deprecated in C++17.\n+using R1 = std::hash<std::variant<int>>::result_type; // { dg-warning \"deprecated\" \"\" { target c++17_only } }\n+using A1 = std::hash<std::variant<int>>::argument_type; // { dg-warning \"deprecated\" \"\" { target c++17_only } }\n+using R2 = std::hash<std::variant<char, int>>::result_type; // { dg-warning \"deprecated\" \"\" { target c++17_only } }\n+using A2 = std::hash<std::variant<char, int>>::argument_type; // { dg-warning \"deprecated\" \"\" { target c++17_only } }\n+#endif\n+\n+// Disabled specializations do not have the nested types.\n+static_assert( has_no_types<S> );\n+static_assert( has_no_types<int, S> );\ndiff --git a/libstdc++-v3/testsuite/20_util/variant/hash_abi.cc b/libstdc++-v3/testsuite/20_util/variant/hash_abi.cc\nnew file mode 100644\nindex 000000000000..19cf72b54de6\n--- /dev/null\n+++ b/libstdc++-v3/testsuite/20_util/variant/hash_abi.cc\n@@ -0,0 +1,48 @@\n+// { dg-options \"-Wdeprecated\" }\n+// { dg-do compile { target c++17 } }\n+\n+#include <variant>\n+\n+struct S { }; // std::hash<S> is a disabled specialization.\n+\n+// Test std::hash size\n+\n+template<typename... Ts>\n+constexpr std::size_t hash_size = sizeof(std::hash<std::variant<Ts...>>);\n+\n+#if _GLIBCXX_INLINE_VERSION\n+// For the unstable ABI the size should always be one.\n+template<std::size_t Size, typename... Ts>\n+constexpr bool check_hash_size = hash_size<Ts...> == 1;\n+#else\n+// For the default ABI, the std::hash specialization has sizeof...(Ts)\n+// base classes of types __hash_empty_base<remove_cv_t<Ts>>... and if\n+// the same type occurs more than once they must have unique addresses.\n+template<std::size_t Size, typename... Ts>\n+constexpr bool check_hash_size = hash_size<Ts...> == Size;\n+#endif\n+\n+static_assert( check_hash_size<1, int> );\n+static_assert( check_hash_size<1, int, long, double> );\n+static_assert( check_hash_size<2, int, long, int> );\n+static_assert( check_hash_size<2, int, long, const int> );\n+static_assert( check_hash_size<3, int, int, const int> );\n+\n+static_assert( check_hash_size<1, S> );\n+static_assert( check_hash_size<1, int, S> );\n+static_assert( check_hash_size<2, int, S, int> );\n+static_assert( check_hash_size<2, int, S, int, S> );\n+static_assert( check_hash_size<2, int, S, S, int> );\n+static_assert( check_hash_size<3, int, S, S, int, S> );\n+\n+// For the default ABI this has two __hash_empty_base<int> base classes,\n+// for the unstable ABI it does not.\n+struct H\n+: std::hash<std::variant<int>>, std::hash<std::variant<long, int>>\n+{ };\n+static_assert( sizeof(H) == hash_size<int, int, long> );\n+// Likewise, even though one of the base classes is a disabled specialization.\n+struct HX\n+: std::hash<std::variant<int>>, std::hash<std::variant<S, int>>\n+{ };\n+static_assert( sizeof(HX) == hash_size<int, S, int> );\n","prefixes":["v1","1/1"]}