{"id":2219744,"url":"http://patchwork.ozlabs.org/api/1.1/patches/2219744/?format=json","web_url":"http://patchwork.ozlabs.org/project/gcc/patch/20260404050329.949729-1-ncm@cantrip.org/","project":{"id":17,"url":"http://patchwork.ozlabs.org/api/1.1/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},"msgid":"<20260404050329.949729-1-ncm@cantrip.org>","date":"2026-04-04T04:52:35","name":"[RFC] libstdc++: Use allocate_at_least in vector, string (P0401) [PR118030]","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"3fedbc15e479123804e7a541bb47e36e8a535bbb","submitter":{"id":90892,"url":"http://patchwork.ozlabs.org/api/1.1/people/90892/?format=json","name":"Nathan Myers","email":"ncm@cantrip.org"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/gcc/patch/20260404050329.949729-1-ncm@cantrip.org/mbox/","series":[{"id":498697,"url":"http://patchwork.ozlabs.org/api/1.1/series/498697/?format=json","web_url":"http://patchwork.ozlabs.org/project/gcc/list/?series=498697","date":"2026-04-04T04:52:35","name":"[RFC] libstdc++: Use allocate_at_least in vector, string (P0401) [PR118030]","version":1,"mbox":"http://patchwork.ozlabs.org/series/498697/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2219744/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2219744/checks/","tags":{},"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;\n dmarc=none (p=none dis=none) header.from=cantrip.org","sourceware.org; spf=fail smtp.mailfrom=cantrip.org","server2.sourceware.org;\n arc=none smtp.remote-ip=205.139.111.44"],"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 4fnk666c3Fz1yCs\n\tfor <incoming@patchwork.ozlabs.org>; Sat, 04 Apr 2026 16:04:27 +1100 (AEDT)","from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id AFA774BA903D\n\tfor <incoming@patchwork.ozlabs.org>; Sat,  4 Apr 2026 05:04:15 +0000 (GMT)","from us-smtp-delivery-44.mimecast.com\n (us-smtp-delivery-44.mimecast.com [205.139.111.44])\n by sourceware.org (Postfix) with ESMTP id 83F1B4BA23E3\n for <gcc-patches@gcc.gnu.org>; Sat,  4 Apr 2026 05:03:37 +0000 (GMT)","from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com\n (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by\n relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3,\n cipher=TLS_AES_256_GCM_SHA384) id us-mta-170-T_ygWAgnOaGS9fx_6aeWmQ-1; Sat,\n 04 Apr 2026 01:03:35 -0400","from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com\n (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12])\n (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest\n SHA256)\n (No client certificate requested)\n by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS\n id 1E6141800281; Sat,  4 Apr 2026 05:03:34 +0000 (UTC)","from redhat.redhat.com (unknown [10.44.34.194])\n by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP\n id 545EC19560A6; Sat,  4 Apr 2026 05:03:30 +0000 (UTC)"],"DKIM-Filter":["OpenDKIM Filter v2.11.0 sourceware.org AFA774BA903D","OpenDKIM Filter v2.11.0 sourceware.org 83F1B4BA23E3"],"DMARC-Filter":"OpenDMARC Filter v1.4.2 sourceware.org 83F1B4BA23E3","ARC-Filter":"OpenARC Filter v1.0.0 sourceware.org 83F1B4BA23E3","ARC-Seal":"i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1775279017; cv=none;\n b=mXHtImf5exZyvE1X5bFPhYpX8kPAOo6yq3L2TTIalPzBZfw0TfY4308NCyoaQc984KP1Jk+mw4jGm8NA5hcMf+o9tTiyvrtSW/pxcoldGJSLV3r0eG1hmLQzmtgFLLRuaS8mJ23p3BMIhGZniPVobVCQo46wpbmMKlolaNYVfVg=","ARC-Message-Signature":"i=1; a=rsa-sha256; d=sourceware.org; s=key;\n t=1775279017; c=relaxed/simple;\n bh=b6WFAPOqWqEVZQGp8vWdGrwFQT1wlAHVUuU1QkP7KNY=;\n h=From:To:Subject:Date:Message-ID:MIME-Version;\n b=FQG3diDmpirpngBq9jGAT9va49PjMbXFgsVZ39ESEuoOVkF+XV7JgG7Ewo8YY7IxEyCYC+MVpI/jfSMuqapxAbjpn3AM931Z89GO1bKB0qa0vVlitg3ck+RPtl2oFzB7bAsOcbhylvG07wuvkjlRpYy6ilU+4Wmd4dSLIArd0o4=","ARC-Authentication-Results":"i=1; server2.sourceware.org","X-MC-Unique":"T_ygWAgnOaGS9fx_6aeWmQ-1","X-Mimecast-MFC-AGG-ID":"T_ygWAgnOaGS9fx_6aeWmQ_1775279014","From":"Nathan Myers <ncm@cantrip.org>","To":"gcc-patches@gcc.gnu.org,\n\tlibstdc++@gcc.gnu.org","Subject":"[RFC] libstdc++: Use allocate_at_least in vector,\n string (P0401) [PR118030]","Date":"Sat,  4 Apr 2026 00:52:35 -0400","Message-ID":"<20260404050329.949729-1-ncm@cantrip.org>","MIME-Version":"1.0","X-Scanned-By":"MIMEDefang 3.0 on 10.30.177.12","X-Mimecast-Spam-Score":"0","X-Mimecast-MFC-PROC-ID":"3G9H8HA3fvyqQyxDQqgu8k1q4_oT1R7STKLdtAZuV0g_1775279014","X-Mimecast-Originator":"cantrip.org","Content-Transfer-Encoding":"quoted-printable","content-type":"text/plain; charset=WINDOWS-1252; x-default=true","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>","Errors-To":"gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org"},"content":"- Implement as much of allocator<>::allocate_at_least as is\n  possible relying solely on known alignment behavior of\n  standard operator new.\n - Use allocator_at_least in string and vector to maximize usage\n  of actually allocated storage, so far as is known.\n\nDefines and uses {string,vector}::_Alloc_result for all c++-*,\nto reduce churn, actually calling allocate_at_least only where\ndefined. User-defined allocators can implement larger benefits,\nand string and vector will take full advantage.\n\nTo do: Add tests; Fix constexpr test failures; By detecting\nwhether ::operator new has been replaced, we may rely on details \nof our allocator implementation.\n\nlibstdc++-v3/ChangeLog:\n\tPR libstdc++/118030\n\t* include/bits/alloc_traits.h (allocate_at_least): Call\n\t__a.allocate_at_least only if it exists.\n\t* include/bits/allocator.h (allocate_at_least): Remove.\n\t* include/bits/basic_string.h (_Alloc_result): Define.\n\t(_S_allocate): Delete.\n\t(_S_allocate_at_least): Define.\n\t(assign): Use _S_allocate_at_least.\n\t* include/bits/basic_string.tcc (_M_create, reserve, _M_replace):\n\tUse _S_allocate_at_least.\n\t* include/bits/memory_resource.h (allocate_at_least): Define.\n\t* include/bits/new_allocator.h (allocate_at_least): Define.\n\t* include/bits/stl_vector.h (_S_max_size): Move to _Vector_base.\n\t(_M_allocate): Remove.\n\t(_M_allocate_at_least): Define.\n\t(_Alloc_result): Define.\n\t(max_size, _S_check_init_len): Use _S_max_size as moved.\n\t(_M_create_storage, append_range, _M_allocate_and_copy,\n\t_M_initialize_dispatch, _M_range_initialize): Use _M_allocate_at_least.\n\t(_M_check_len): Improve logic.\n\t* include/bits/vector.tcc (reserve, _M_realloc_insert,\n\t_M_realloc_append, _M_fill_insert, _M_fill_append, _M_default_append,\n\t_M_range_insert, insert_range): Use _M_allocate_at_least.\n\t* include/std/string: Define __glibcxx_want_allocate_at_least.\n\t* include/std/vector: Same.\n\t* testsuite/21_strings/basic_string/cons/wchar_t/constexpr.cc:\n\t* testsuite/util/testsuite_allocator.h (allocate_at_least (2x)): Define.\n\t(allocate): Use allocate_at_least.\n---\n libstdc++-v3/include/bits/alloc_traits.h      |  9 +-\n libstdc++-v3/include/bits/allocator.h         |  6 --\n libstdc++-v3/include/bits/basic_string.h      | 24 +++--\n libstdc++-v3/include/bits/basic_string.tcc    | 17 ++--\n libstdc++-v3/include/bits/memory_resource.h   | 16 ++++\n libstdc++-v3/include/bits/new_allocator.h     | 43 +++++++++\n libstdc++-v3/include/bits/stl_vector.h        | 95 ++++++++++++-------\n libstdc++-v3/include/bits/vector.tcc          | 66 ++++++++-----\n libstdc++-v3/include/std/string               |  1 +\n libstdc++-v3/include/std/vector               |  1 +\n .../basic_string/cons/wchar_t/constexpr.cc    |  2 +-\n .../testsuite/util/testsuite_allocator.h      | 40 ++++++++\n 12 files changed, 239 insertions(+), 81 deletions(-)","diff":"diff --git a/libstdc++-v3/include/bits/alloc_traits.h b/libstdc++-v3/include/bits/alloc_traits.h\nindex 2be8ed561d4..b773be365bc 100644\n--- a/libstdc++-v3/include/bits/alloc_traits.h\n+++ b/libstdc++-v3/include/bits/alloc_traits.h\n@@ -419,7 +419,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION\n       */\n       [[nodiscard]] static constexpr auto\n       allocate_at_least(_Alloc& __a, size_type __n)\n-\t-> allocation_result<pointer, size_type>\n+      -> allocation_result<pointer, size_type>\n       {\n \tif constexpr (requires { __a.allocate_at_least(__n); })\n \t  return __a.allocate_at_least(__n);\n@@ -672,7 +672,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION\n       [[nodiscard]] static constexpr auto\n       allocate_at_least(allocator_type __a, size_type __n)\n \t-> allocation_result<pointer, size_type>\n-      { return __a.allocate_at_least(__n); }\n+      {\n+\tif constexpr(requires { __a.__allocate_at_least(__n); })\n+\t  return __a.allocate_at_least(__n);\n+\telse\n+\t  return { __a.allocate(__n), __n };\n+      }\n #endif\n \n       /**\ndiff --git a/libstdc++-v3/include/bits/allocator.h b/libstdc++-v3/include/bits/allocator.h\nindex 9c22c805ebe..ec160f44a1e 100644\n--- a/libstdc++-v3/include/bits/allocator.h\n+++ b/libstdc++-v3/include/bits/allocator.h\n@@ -219,12 +219,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION\n       }\n #endif // C++20\n \n-#ifdef __glibcxx_allocate_at_least  // C++23\n-      [[nodiscard]] constexpr allocation_result<_Tp*, size_t>\n-      allocate_at_least(size_t __n)\n-      { return { this->allocate(__n), __n }; }\n-#endif\n-\n       friend __attribute__((__always_inline__)) _GLIBCXX20_CONSTEXPR\n       bool\n       operator==(const allocator&, const allocator&) _GLIBCXX_NOTHROW\ndiff --git a/libstdc++-v3/include/bits/basic_string.h b/libstdc++-v3/include/bits/basic_string.h\nindex 202a911f9ef..09a87d27910 100644\n--- a/libstdc++-v3/include/bits/basic_string.h\n+++ b/libstdc++-v3/include/bits/basic_string.h\n@@ -135,10 +135,18 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11\n #endif\n \n     private:\n-      static _GLIBCXX20_CONSTEXPR pointer\n-      _S_allocate(_Char_alloc_type& __a, size_type __n)\n+      struct _Alloc_result { pointer __ptr; size_type __count; };\n+\n+      static _GLIBCXX20_CONSTEXPR _Alloc_result\n+      _S_allocate_at_least(_Char_alloc_type& __a, size_type __n)\n       {\n-\tpointer __p = _Alloc_traits::allocate(__a, __n);\n+\t_Alloc_result __r;\n+#ifdef __glibcxx_allocate_at_least  // C++23\n+\tauto [__ptr, __count] = _Alloc_traits::allocate_at_least(__a, __n);\n+\t__r.__ptr = __ptr, __r.__count = __count;\n+#else\n+\t__r.__ptr = _Alloc_traits::allocate(__a, __n), __r.__count = __n;\n+#endif\n #if __glibcxx_constexpr_string >= 201907L\n \t// std::char_traits begins the lifetime of characters,\n \t// but custom traits might not, so do it here.\n@@ -146,9 +154,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11\n \t  if (std::__is_constant_evaluated())\n \t    // Begin the lifetime of characters in allocated storage.\n \t    for (size_type __i = 0; __i < __n; ++__i)\n-\t      std::construct_at(__builtin_addressof(__p[__i]));\n+\t      std::construct_at(__builtin_addressof(__r.__ptr[__i]));\n #endif\n-\treturn __p;\n+\treturn __r;\n       }\n \n #ifdef __glibcxx_string_view // >= C++17\n@@ -1782,10 +1790,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11\n \t\t    const auto __len = __str.size();\n \t\t    auto __alloc = __str._M_get_allocator();\n \t\t    // If this allocation throws there are no effects:\n-\t\t    auto __ptr = _S_allocate(__alloc, __len + 1);\n+\t\t    auto __r = _S_allocate_at_least(__alloc, __len + 1);\n \t\t    _M_destroy(_M_allocated_capacity);\n-\t\t    _M_data(__ptr);\n-\t\t    _M_capacity(__len);\n+\t\t    _M_data(__r.__ptr);\n+\t\t    _M_capacity(__r.__count);\n \t\t    _M_set_length(__len);\n \t\t  }\n \t      }\ndiff --git a/libstdc++-v3/include/bits/basic_string.tcc b/libstdc++-v3/include/bits/basic_string.tcc\nindex a223edf67ac..c56b214a3fd 100644\n--- a/libstdc++-v3/include/bits/basic_string.tcc\n+++ b/libstdc++-v3/include/bits/basic_string.tcc\n@@ -161,7 +161,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION\n \n       // NB: Need an array of char_type[__capacity], plus a terminating\n       // null char_type() element.\n-      return _S_allocate(_M_get_allocator(), __capacity + 1);\n+      _Alloc_result __r = _S_allocate_at_least(\n+\t_M_get_allocator(), __capacity + 1);\n+      __capacity = __r.__count - 1;\n+      return __r.__ptr;\n     }\n \n   // NB: This is the special case for Input Iterators, used in\n@@ -444,11 +447,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION\n       else if (__length < __capacity)\n \ttry\n \t  {\n-\t    pointer __tmp = _S_allocate(_M_get_allocator(), __length + 1);\n-\t    this->_S_copy(__tmp, _M_data(), __length + 1);\n+\t    _Alloc_result __r = _S_allocate_at_least(\n+\t      _M_get_allocator(), __length + 1);\n+\t    this->_S_copy(__r.__ptr, _M_data(), __length + 1);\n \t    _M_dispose();\n-\t    _M_data(__tmp);\n-\t    _M_capacity(__length);\n+\t    _M_data(__r.__ptr);\n+\t    _M_capacity(__r.__count - 1);  // reserve room for NUL.\n \t  }\n \tcatch (const __cxxabiv1::__forced_unwind&)\n \t  { throw; }\n@@ -588,7 +592,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION\n #if __cpp_lib_is_constant_evaluated\n \t  if (std::is_constant_evaluated())\n \t    {\n-\t      auto __newp = _S_allocate(_M_get_allocator(), __new_size);\n+\t      auto __newp =\n+\t\t_S_allocate_at_least(_M_get_allocator(), __new_size).__ptr;\n \t      _S_copy(__newp, this->_M_data(), __pos);\n \t      _S_copy(__newp + __pos, __s, __len2);\n \t      _S_copy(__newp + __pos + __len2, __p + __len1, __how_much);\ndiff --git a/libstdc++-v3/include/bits/memory_resource.h b/libstdc++-v3/include/bits/memory_resource.h\nindex e5c6697b07e..ee3872e2bcd 100644\n--- a/libstdc++-v3/include/bits/memory_resource.h\n+++ b/libstdc++-v3/include/bits/memory_resource.h\n@@ -468,6 +468,22 @@ namespace pmr\n       allocate(allocator_type& __a, size_type __n, const_void_pointer)\n       { return __a.allocate(__n); }\n \n+#ifdef __glibcxx_allocate_at_least\n+      /**\n+       *  @brief  Allocate memory, generously.\n+       *  @param  __a  An allocator.\n+       *  @param  __n  The number of objects to allocate space for.\n+       *  @return Memory of suitable size and alignment for `n` objects\n+       *          of type `value_type`.\n+       *\n+       *  Returns `a.allocate(n)`.\n+      */\n+      [[nodiscard]] static auto\n+      allocate_at_least(allocator_type& __a, size_type __n)\n+\t-> std::allocation_result<pointer, size_type>\n+      { return { __a.allocate(__n), __n }; }\n+#endif\n+\n       /**\n        *  @brief  Deallocate memory.\n        *  @param  __a  An allocator.\ndiff --git a/libstdc++-v3/include/bits/new_allocator.h b/libstdc++-v3/include/bits/new_allocator.h\nindex fbe03e392aa..13018036e3a 100644\n--- a/libstdc++-v3/include/bits/new_allocator.h\n+++ b/libstdc++-v3/include/bits/new_allocator.h\n@@ -162,6 +162,49 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION\n \t  return static_cast<_Tp*>(_GLIBCXX_OPERATOR_NEW(__n * sizeof(_Tp)));\n       }\n \n+#ifdef __glibcxx_allocate_at_least  // C++23\n+      [[nodiscard]] constexpr auto\n+      allocate_at_least(size_t __n)\n+      -> std::allocation_result<_Tp*, size_t>\n+      {\n+\tstatic_assert(requires { sizeof(_Tp); },\n+\t  \"cannot allocate incomplete types\");\n+\n+\tconst std::size_t __align_mask = __STDCPP_DEFAULT_NEW_ALIGNMENT__ - 1;\n+\n+\tif constexpr (!requires { sizeof(_Tp); })\n+\t  return { nullptr, 0 }; // static_assert already failed\n+\telse if (__builtin_expect(__n > this->_M_max_size(), false))\n+\t  {\n+\t    if (__n > (std::size_t(-1) / sizeof(_Tp)))\n+\t      std::__throw_bad_array_new_length();\n+\t    std::__throw_bad_alloc();\n+\t  }\n+\telse if constexpr (alignof(_Tp) > __STDCPP_DEFAULT_NEW_ALIGNMENT__)\n+\t  {\n+\t    const std::align_val_t __al = std::align_val_t(alignof(_Tp));\n+\t    auto __p = _GLIBCXX_OPERATOR_NEW(__n * sizeof(_Tp), __al);\n+\t    return { static_cast<_Tp*>(__p), __n };\n+\t  }\n+\telse if constexpr (sizeof(_Tp) > __align_mask)\n+\t  {\n+\t    auto __p = _GLIBCXX_OPERATOR_NEW(__n * sizeof(_Tp));\n+\t    return { static_cast<_Tp*>(__p), __n };\n+\t  }\n+\telse // See if more _Tp elements can fit into the allocation.\n+\t  {\n+\t    const auto __need = __n * sizeof(_Tp);\n+\t    const auto __ask = (__need + __align_mask) & ~__align_mask;\n+\t    const auto __p = _GLIBCXX_OPERATOR_NEW(__ask);\n+\t    using _Uchar = const unsigned char;\n+\t    static_assert(sizeof(_Tp) <= _Uchar(-1));\n+\t    // Use 8-bit division.\n+\t    _Uchar __spare = __ask - __need, __size = sizeof(_Tp);\n+\t    return { static_cast<_Tp*>(__p), __n + __spare / __size };\n+\t  }\n+      }\n+#endif\n+\n       // __p is not permitted to be a null pointer.\n       _GLIBCXX20_CONSTEXPR void\n       deallocate(_Tp* __p, size_type __n __attribute__ ((__unused__)))\ndiff --git a/libstdc++-v3/include/bits/stl_vector.h b/libstdc++-v3/include/bits/stl_vector.h\nindex c4ca214752a..571018cb6f7 100644\n--- a/libstdc++-v3/include/bits/stl_vector.h\n+++ b/libstdc++-v3/include/bits/stl_vector.h\n@@ -317,6 +317,19 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n       get_allocator() const _GLIBCXX_NOEXCEPT\n       { return allocator_type(_M_get_Tp_allocator()); }\n \n+      static _GLIBCXX20_CONSTEXPR size_t\n+      _S_max_size(const _Tp_alloc_type& __a) _GLIBCXX_NOEXCEPT\n+      {\n+\t// std::distance(begin(), end()) cannot be greater than PTRDIFF_MAX,\n+\t// and realistically we can't store more than PTRDIFF_MAX/sizeof(T)\n+\t// (even if std::allocator_traits::max_size says we can).\n+\tconst size_t __diffmax =\n+\t  __gnu_cxx::__numeric_traits<ptrdiff_t>::__max / sizeof(_Tp);\n+\tconst size_t __allocmax =\n+\t  __gnu_cxx::__alloc_traits<_Alloc>::max_size(__a);\n+\treturn (std::min)(__diffmax, __allocmax);\n+      }\n+\n #if __cplusplus >= 201103L\n       _Vector_base() = default;\n #else\n@@ -381,12 +394,31 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n     public:\n       _Vector_impl _M_impl;\n \n+      struct _Alloc_result { pointer __ptr; size_t __count; };\n+\n       _GLIBCXX20_CONSTEXPR\n-      pointer\n-      _M_allocate(size_t __n)\n+      _Alloc_result\n+      _M_allocate_at_least(size_t __n)\n       {\n \ttypedef __gnu_cxx::__alloc_traits<_Tp_alloc_type> _Tr;\n-\treturn __n != 0 ? _Tr::allocate(_M_impl, __n) : pointer();\n+\t_Alloc_result __r;\n+\tif (__builtin_expect(__n != 0, true))\n+\t  {\n+#ifdef __glibcxx_allocate_at_least  // C++23\n+\t    auto [__ptr, __count] = _Tr::allocate_at_least(_M_impl, __n);\n+\t    if (__count > __n)\n+\t      {\n+\t\tsize_t __max = _S_max_size(_M_get_Tp_allocator());\n+\t\tif (__builtin_expect(__count > __max, false))\n+\t\t  __count = __max;\n+\t      }\n+\t    __r.__ptr = __ptr, __r.__count = __count;\n+#else\n+\t    __r.__ptr = _Tr::allocate(_M_impl, __n), __r.__count = __n;\n+#endif\n+\t  }\n+\telse __r.__ptr = pointer(), __r.__count = 0;\n+\treturn __r;\n       }\n \n       _GLIBCXX20_CONSTEXPR\n@@ -404,9 +436,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n       void\n       _M_create_storage(size_t __n)\n       {\n-\tthis->_M_impl._M_start = this->_M_allocate(__n);\n-\tthis->_M_impl._M_finish = this->_M_impl._M_start;\n-\tthis->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;\n+\t_Alloc_result __r = this->_M_allocate_at_least(__n);\n+\tthis->_M_impl._M_finish = this->_M_impl._M_start = __r.__ptr;\n+\tthis->_M_impl._M_end_of_storage = this->_M_impl._M_start + __r.__count;\n       }\n \n #if __glibcxx_containers_ranges // C++ >= 23\n@@ -480,6 +512,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n       typedef _Vector_base<_Tp, _Alloc>\t\t\t_Base;\n       typedef typename _Base::_Tp_alloc_type\t\t_Tp_alloc_type;\n       typedef __gnu_cxx::__alloc_traits<_Tp_alloc_type>\t_Alloc_traits;\n+      typedef typename _Base::_Alloc_result             _Alloc_result;\n \n     public:\n       typedef _Tp\t\t\t\t\tvalue_type;\n@@ -535,7 +568,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n #endif // C++11\n \n     protected:\n-      using _Base::_M_allocate;\n+      using _Base::_M_allocate_at_least;\n       using _Base::_M_deallocate;\n       using _Base::_M_impl;\n       using _Base::_M_get_Tp_allocator;\n@@ -1116,7 +1149,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n       _GLIBCXX_NODISCARD _GLIBCXX20_CONSTEXPR\n       size_type\n       max_size() const _GLIBCXX_NOEXCEPT\n-      { return _S_max_size(_M_get_Tp_allocator()); }\n+      { return _Base::_S_max_size(_M_get_Tp_allocator()); }\n \n #if __cplusplus >= 201103L\n       /**\n@@ -1682,16 +1715,18 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n \t\t  return;\n \t\t}\n \n-\t      const size_type __len = _M_check_len(__n, \"vector::append_range\");\n+\t      const size_type __len1 = _M_check_len(__n, \"vector::append_range\");\n \n \t      pointer __old_start = this->_M_impl._M_start;\n \t      pointer __old_finish = this->_M_impl._M_finish;\n \n-\t      allocator_type& __a = _M_get_Tp_allocator();\n-\t      const pointer __start = this->_M_allocate(__len);\n+\t      auto [__ptr, __count] = this->_M_allocate_at_least(__len1);\n+\t      const size_type __len = __count;\n+\t      const pointer __start = __ptr;\n \t      const pointer __mid = __start + __sz;\n \t      const pointer __back = __mid + __n;\n \t      _Guard_alloc __guard(__start, __len, *this);\n+\t      allocator_type& __a = _M_get_Tp_allocator();\n \t      std::__uninitialized_copy_a(ranges::begin(__rg),\n \t\t\t\t\t  ranges::end(__rg),\n \t\t\t\t\t  __mid, __a);\n@@ -1897,7 +1932,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n \t_M_allocate_and_copy(size_type __n,\n \t\t\t     _ForwardIterator __first, _ForwardIterator __last)\n \t{\n-\t  _Guard_alloc __guard(this->_M_allocate(__n), __n, *this);\n+\t  _Alloc_result __r = this->_M_allocate_at_least(__n);\n+\t  _Guard_alloc __guard(__r.__ptr, __r.__count, *this);\n \t  std::__uninitialized_copy_a\n \t    (__first, __last, __guard._M_storage, _M_get_Tp_allocator());\n \t  return __guard._M_release();\n@@ -1916,8 +1952,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n \t_M_initialize_dispatch(_Integer __int_n, _Integer __value, __true_type)\n \t{\n \t  const size_type __n = static_cast<size_type>(__int_n);\n-\t  pointer __start =\n-\t    _M_allocate(_S_check_init_len(__n, _M_get_Tp_allocator()));\n+\t  pointer __start = _M_allocate_at_least(\n+\t      _S_check_init_len(__n, _M_get_Tp_allocator())).__ptr;\n \t  this->_M_impl._M_start = __start;\n \t  this->_M_impl._M_end_of_storage = __start + __n;\n \t  _M_fill_initialize(__n, __value);\n@@ -1971,10 +2007,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n \t_M_range_initialize_n(_Iterator __first, _Sentinel __last,\n \t\t\t      size_type __n)\n \t{\n-\t  pointer __start =\n-\t    this->_M_allocate(_S_check_init_len(__n, _M_get_Tp_allocator()));\n+\t  _Alloc_result __r = this->_M_allocate_at_least(\n+\t    _S_check_init_len(__n, _M_get_Tp_allocator()));\n+\t  pointer __start = __r.__ptr;\n \t  this->_M_impl._M_start = this->_M_impl._M_finish = __start;\n-\t  this->_M_impl._M_end_of_storage = __start + __n;\n+\t  this->_M_impl._M_end_of_storage = __start + __r.__count;\n \t  this->_M_impl._M_finish\n \t      = std::__uninitialized_copy_a(_GLIBCXX_MOVE(__first), __last,\n \t\t\t\t\t    __start, _M_get_Tp_allocator());\n@@ -2191,35 +2228,27 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n       size_type\n       _M_check_len(size_type __n, const char* __s) const\n       {\n-\tif (max_size() - size() < __n)\n+\tconst size_type __room = max_size() - size();\n+\tif (__room < __n)\n \t  __throw_length_error(__N(__s));\n \n-\tconst size_type __len = size() + (std::max)(size(), __n);\n-\treturn (__len < size() || __len > max_size()) ? max_size() : __len;\n+\tif (__n < size())\n+\t  __n = size();  // Grow by (at least) doubling ...\n+\tif (__n > __room)\n+\t  __n = __room;  //  ... but only as much as will fit.\n+\treturn size() + __n;\n       }\n \n       // Called by constructors to check initial size.\n       static _GLIBCXX20_CONSTEXPR size_type\n       _S_check_init_len(size_type __n, const allocator_type& __a)\n       {\n-\tif (__n > _S_max_size(_Tp_alloc_type(__a)))\n+\tif (__n > _Base::_S_max_size(_Tp_alloc_type(__a)))\n \t  __throw_length_error(\n \t      __N(\"cannot create std::vector larger than max_size()\"));\n \treturn __n;\n       }\n \n-      static _GLIBCXX20_CONSTEXPR size_type\n-      _S_max_size(const _Tp_alloc_type& __a) _GLIBCXX_NOEXCEPT\n-      {\n-\t// std::distance(begin(), end()) cannot be greater than PTRDIFF_MAX,\n-\t// and realistically we can't store more than PTRDIFF_MAX/sizeof(T)\n-\t// (even if std::allocator_traits::max_size says we can).\n-\tconst size_t __diffmax\n-\t  = __gnu_cxx::__numeric_traits<ptrdiff_t>::__max / sizeof(_Tp);\n-\tconst size_t __allocmax = _Alloc_traits::max_size(__a);\n-\treturn (std::min)(__diffmax, __allocmax);\n-      }\n-\n       // Internal erase functions follow.\n \n       // Called by erase(q1,q2), clear(), resize(), _M_fill_assign,\ndiff --git a/libstdc++-v3/include/bits/vector.tcc b/libstdc++-v3/include/bits/vector.tcc\nindex b790fca2964..9524038b7d1 100644\n--- a/libstdc++-v3/include/bits/vector.tcc\n+++ b/libstdc++-v3/include/bits/vector.tcc\n@@ -79,7 +79,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n #if __cplusplus >= 201103L\n \t  if constexpr (_S_use_relocate())\n \t    {\n-\t      __tmp = this->_M_allocate(__n);\n+\t      auto __res = this->_M_allocate_at_least(__n);\n+\t      __tmp = __res.__ptr;\n+\t      __n = __res.__count;\n \t      std::__relocate_a(this->_M_impl._M_start, this->_M_impl._M_finish,\n \t\t\t\t__tmp, _M_get_Tp_allocator());\n \t    }\n@@ -106,12 +108,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n #if __cplusplus >= 201103L\n   template<typename _Tp, typename _Alloc>\n     template<typename... _Args>\n-#if __cplusplus > 201402L\n+# if __cplusplus > 201402L\n       _GLIBCXX20_CONSTEXPR\n       typename vector<_Tp, _Alloc>::reference\n-#else\n+# else\n       void\n-#endif\n+# endif\n       vector<_Tp, _Alloc>::\n       emplace_back(_Args&&... __args)\n       {\n@@ -125,11 +127,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n \t  }\n \telse\n \t  _M_realloc_append(std::forward<_Args>(__args)...);\n-#if __cplusplus > 201402L\n+# if __cplusplus > 201402L\n \treturn back();\n-#endif\n+# endif\n       }\n-#endif\n+#endif  // __cplusplus >= 201103L\n \n   template<typename _Tp, typename _Alloc>\n     _GLIBCXX20_CONSTEXPR\n@@ -464,13 +466,15 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n     _M_realloc_insert(iterator __position, const _Tp& __x)\n #endif\n     {\n-      const size_type __len = _M_check_len(1u, \"vector::_M_realloc_insert\");\n-      if (__len <= 0)\n+      const size_type __len1 = _M_check_len(1u, \"vector::_M_realloc_insert\");\n+      if (__len1 <= 0)\n \t__builtin_unreachable();\n       pointer __old_start = this->_M_impl._M_start;\n       pointer __old_finish = this->_M_impl._M_finish;\n       const size_type __elems_before = __position - begin();\n-      pointer __new_start(this->_M_allocate(__len));\n+      _Alloc_result __r = this->_M_allocate_at_least(__len1);\n+      const size_type __len = __r.__count;\n+      pointer __new_start(__r.__ptr);\n       pointer __new_finish(__new_start);\n \n       {\n@@ -574,14 +578,16 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n       const size_type __len = _M_check_len(1u, \"vector::_M_realloc_append\");\n       if (__len <= 0)\n \t__builtin_unreachable();\n-      pointer __old_start = this->_M_impl._M_start;\n-      pointer __old_finish = this->_M_impl._M_finish;\n+      const pointer __old_start = this->_M_impl._M_start;\n+      const pointer __old_finish = this->_M_impl._M_finish;\n       const size_type __elems = size();\n-      pointer __new_start(this->_M_allocate(__len));\n+      const _Alloc_result __r = this->_M_allocate_at_least(__len);\n+      const size_type __rlen = __r.__count;\n+      const pointer __new_start(__r.__ptr);\n       pointer __new_finish(__new_start);\n \n       {\n-\t_Guard_alloc __guard(__new_start, __len, *this);\n+\t_Guard_alloc __guard(__new_start, __rlen, *this);\n \n \t// The order of the three operations is dictated by the C++11\n \t// case, where the moves could alter a new element belonging\n@@ -652,7 +658,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n \n       this->_M_impl._M_start = __new_start;\n       this->_M_impl._M_finish = __new_finish;\n-      this->_M_impl._M_end_of_storage = __new_start + __len;\n+      this->_M_impl._M_end_of_storage = __new_start + __rlen;\n     }\n #pragma GCC diagnostic pop\n \n@@ -716,10 +722,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n \t      pointer __old_finish = this->_M_impl._M_finish;\n \t      const pointer __pos = __position.base();\n \n-\t      const size_type __len =\n+\t      const size_type __len1 =\n \t\t_M_check_len(__n, \"vector::_M_fill_insert\");\n \t      const size_type __elems_before = __pos - __old_start;\n-\t      pointer __new_start(this->_M_allocate(__len));\n+\t      _Alloc_result __r = this->_M_allocate_at_least(__len1);\n+\t      const size_type __len = __r.__count;\n+\t      pointer __new_start(__r.__ptr);\n \t      pointer __new_finish(__new_start);\n \t      __try\n \t\t{\n@@ -787,7 +795,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n \n \t   const size_type __len =\n \t     _M_check_len(__n, \"vector::_M_fill_append\");\n-\t   pointer __new_start(this->_M_allocate(__len));\n+\t   _Alloc_result __r = this->_M_allocate_at_least(__len);\n+\t   __len = __r.__count;\n+\t   pointer __new_start(__r.__ptr);\n \t   pointer __new_finish(__new_start + __old_size);\n \t   __try\n \t     {\n@@ -852,9 +862,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n \t      pointer __old_start = this->_M_impl._M_start;\n \t      pointer __old_finish = this->_M_impl._M_finish;\n \n-\t      const size_type __len =\n+\t      const size_type __len1 =\n \t\t_M_check_len(__n, \"vector::_M_default_append\");\n-\t      pointer __new_start(this->_M_allocate(__len));\n+\t      _Alloc_result __r = this->_M_allocate_at_least(__len1);\n+\t      const size_type __len = __r.__count;\n+\t      pointer __new_start(__r.__ptr);\n \n \t      {\n \t\t_Guard_alloc __guard(__new_start, __len, *this);\n@@ -1003,14 +1015,16 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n \t\tpointer __old_start = this->_M_impl._M_start;\n \t\tpointer __old_finish = this->_M_impl._M_finish;\n \n-\t\tconst size_type __len =\n+\t\tconst size_type __len1 =\n \t\t  _M_check_len(__n, \"vector::_M_range_insert\");\n #if __cplusplus < 201103L\n-\t\tif (__len < (__n + (__old_finish - __old_start)))\n+\t\tif (__len1 < (__n + (__old_finish - __old_start)))\n \t\t  __builtin_unreachable();\n #endif\n \n-\t\tpointer __new_start(this->_M_allocate(__len));\n+\t\t_Alloc_result __r = this->_M_allocate_at_least(__len1);\n+\t\tconst size_type __len = __r.__count;\n+\t\tpointer __new_start(__r.__ptr);\n \t\tpointer __new_finish(__new_start);\n \t\t__try\n \t\t  {\n@@ -1111,7 +1125,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n \t      }\n \t    else // Reallocate\n \t      {\n-\t\tconst size_type __len\n+\t\tconst size_type __len1\n \t\t  = _M_check_len(__n, \"vector::insert_range\");\n \n \t\tstruct _Guard : _Guard_alloc\n@@ -1130,7 +1144,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER\n \t\t};\n \n \t\t// Allocate new storage:\n-\t\tpointer __new_start(this->_M_allocate(__len));\n+\t\t_Alloc_result __r = this->_M_allocate_at_least(__len1);\n+\t\tconst size_type __len = __r.__count;\n+\t\tpointer __new_start(__r.__ptr);\n \t\t_Guard __guard(__new_start, __len, *this);\n \n \t\tauto& __alloc = _M_get_Tp_allocator();\ndiff --git a/libstdc++-v3/include/std/string b/libstdc++-v3/include/std/string\nindex c2b37391fc7..da50bce2e7d 100644\n--- a/libstdc++-v3/include/std/string\n+++ b/libstdc++-v3/include/std/string\n@@ -67,6 +67,7 @@\n #endif\n \n #define __glibcxx_want_algorithm_default_value_type\n+#define __glibcxx_want_allocate_at_least\n #define __glibcxx_want_allocator_traits_is_always_equal\n #define __glibcxx_want_constexpr_char_traits\n #define __glibcxx_want_constexpr_string\ndiff --git a/libstdc++-v3/include/std/vector b/libstdc++-v3/include/std/vector\nindex 343483e9519..ac041cc9408 100644\n--- a/libstdc++-v3/include/std/vector\n+++ b/libstdc++-v3/include/std/vector\n@@ -79,6 +79,7 @@\n #endif\n \n #define __glibcxx_want_algorithm_default_value_type\n+#define __glibcxx_want_allocate_at_least\n #define __glibcxx_want_allocator_traits_is_always_equal\n #define __glibcxx_want_constexpr_vector\n #define __glibcxx_want_containers_ranges\ndiff --git a/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/constexpr.cc b/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/constexpr.cc\nindex 44c8391ebc2..9942e1cfb6a 100644\n--- a/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/constexpr.cc\n+++ b/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/constexpr.cc\n@@ -34,7 +34,7 @@ struct Alloc : std::allocator<T>\n   { return personality == a.personality; }\n };\n \n-constexpr bool\n+consteexpr bool\n test_default_ctor()\n {\n   std::basic_string<C> s0;\ndiff --git a/libstdc++-v3/testsuite/util/testsuite_allocator.h b/libstdc++-v3/testsuite/util/testsuite_allocator.h\nindex 892a385e307..d8914229c7a 100644\n--- a/libstdc++-v3/testsuite/util/testsuite_allocator.h\n+++ b/libstdc++-v3/testsuite/util/testsuite_allocator.h\n@@ -176,6 +176,16 @@ namespace __gnu_test\n \treturn p;\n       }\n \n+#ifdef __glibcxx_allocate_at_least\n+      std::allocation_result<pointer, size_type>\n+      allocate_at_least(size_type n)\n+      {\n+\tauto r = AllocTraits::allocate_at_least(*this, n);\n+\tcounter_type::allocate(r.count * sizeof(T));\n+\treturn r;\n+      }\n+#endif\n+\n #if __cplusplus >= 201103L\n       template<typename U, typename... Args>\n \tvoid\n@@ -353,6 +363,9 @@ namespace __gnu_test\n       _GLIBCXX20_CONSTEXPR\n       pointer\n       allocate(size_type n, const void* = 0)\n+#ifdef __glibcxx_allocate_at_least\n+      { return this->allocate_at_least(n).ptr; }\n+#else\n       {\n \tpointer p = AllocTraits::allocate(*this, n);\n \n@@ -372,6 +385,33 @@ namespace __gnu_test\n \n \treturn p;\n       }\n+#endif\n+\n+#ifdef __glibcxx_allocate_at_least\n+      _GLIBCXX20_CONSTEXPR\n+      auto\n+      allocate_at_least(size_type n)\n+      -> std::allocation_result<Tp*, size_t>\n+      {\n+\tauto r = AllocTraits::allocate_at_least(*this, n);\n+\n+\tif (std::__is_constant_evaluated())\n+\t  return r;\n+\n+\ttry\n+\t  {\n+\t    get_map().insert(map_type::value_type(\n+\t\t  reinterpret_cast<void*>(r.ptr), personality));\n+\t  }\n+\tcatch(...)\n+\t  {\n+\t    AllocTraits::deallocate(*this, r.ptr, r.count);\n+\t    __throw_exception_again;\n+\t  }\n+\n+\treturn r;\n+      }\n+#endif\n \n       _GLIBCXX14_CONSTEXPR\n       void\n","prefixes":["RFC"]}