[PATCH] libstdc++: Use allocate_at_least in vector, string (P0401) [PR118030]

Jonathan Wakely jwakely@redhat.com
Mon Apr 20 14:16:08 GMT 2026


On Sat, 18 Apr 2026 at 17:56 -0400, Nathan Myers wrote:
>Changes in v2:
> - Avoid PR108377 "exceeds maximum object size" warning
>  differently, contained to __allocate_to_alignment.
> - Rename _S_allocate_to_alignment to __allocate_to_alignment
>  because it is not a member. Generalize it for arbitrary pointer
>  and size types.
> - Declare allocator_traits<allocator<void>>::allocate_at_least
>  with " = delete;", matching others.
> - In allocator_traits<>::allocate_at_least, de-structure and
>  reconstruct the allocation result to enable an implicit
>  pointer type conversion on the way, as used (e.g.) for
>  testsuite_allocator.h.
> - Define allocate_at_least in the polymorphic specialization
>  of allocator_traits (bits/memory_resource.h), and in the
>  testsuite allocator, destructuring likewise.
> - In string:
>    - Make _M_create use _M_allocate_at_least. (This function
>     is no longer used, but is retained for ABI stability.)
>    - Define (in addition to _M_create_plus) a new allocation
>     helper function _M_create_and_place to abstract common
>     operations, and use it where suitable.
>    - Add new member signatures to config/abi/pre/gnu.ver.
>    - Relax shrink_to_fit()/reserve() to allow a little extra.
> - In vector, define (on top of _S_allocate_at_least) helper
>  functions _M_allocate_and_copy, _M_displace_storage,
>  and _M_allocate_and_migrate to abstract common ops, and
>  use them. This incidentally corrects an (apparent?) omission
>  of _GLIBCXX_ASAN_ANNOTATE_REINIT in operator=.
> - Adjust tests to pass with or without allocate_at_least.
>  (__STDCPP_DEFAULT_NEW_ALIGNMENT__ is not defined everywhere.)
>
>Changes from RFC:
> - Improve doxygen for new interfaces.
> - Move code implementing alignment size-round-up logic from
>  allocator.h to alloc_traits.h for other allocators' reuse.
> - Restore allocator.h definition of allocate_at_least, that
>  then explicitly delegates to its base class implementation.
> - Retain existing string::_S_allocate, vector<>::_M_allocate
>  interfaces for ABI stability, using new names
>  _S_allocate_at_least, _M_allocate_at_least for new behavior.
> - Define string _M_create_plus using S_allocate_at_least, and
>  use that throughout in place of _M_create, retaining _M_create
>  for ABI stability.
> - Make memory_resource::allocate_at_least use argument
>  allocator's allocate_at_least if present.
> - Fix std::allocator<>::allocate_at_least so it takes its
>  allocator object by reference, not by value.
> - Per review, in constexpr context begin lifetime of all
>  potential string characters upon allocation.
> - Revert "< 2011"-only vector<>::_M_initialize_dispatch changes.
> - Fix string new-capacity off-by-one errors.
> - Export new symbols _S_allocate_at_least and _M_create_plus from
>  bits/basic_string.h.
> - Work around PR108377 spurious "exceeds maximum object size".
> - Test.
> - Patch too-strict capacity tests.
>
>Implement as much of allocator<>::allocate_at_least as possible
>relying solely on known alignment behavior of standard operator
>new. Provide apparatus for users' allocators to do the same.
>
>Use allocator_at_least in string and vector to maximize usage of
>actually allocated storage, as revealed by the allocator in use.
>For user-supplied allocators this may make a big difference.
>
>Fixes omitted _GLIBCXX_ASAN_ANNOTATE_REINIT in vector<>::operator=.
>
>Nothing is changed in include/ext/malloc_allocator or others.
>They can be updated at leisure, piecemeal.
>
>libstdc++-v3/ChangeLog:
>	PR libstdc++/118030
>	* include/bits/alloc_traits.h (__allocate_to_alignment): Define.
>	(allocate_at_least): In allocator_traits template member,
>	destructure and reconstruct result from allocator object's version,
>	to enable pointer type conversion.
>	(allocate_at_least): Delegate, in allocator_traits<allocator<_Tp>>
>	specialization, to member where defined.
>	(allocate_at_least): Declare "= delete;" in allocator<void>.
>	* include/bits/allocator.h (allocate_at_least): Delegate to base
>	allocate_at_least where defined, calling with explicit base-class
>	qualification, picking up __new_allocator member.
>	* include/bits/basic_string.h (_Alloc_result): Define new type.
>	(_S_allocate_at_least): Define.
>	(_S_allocate): Delegate to _S_allocate_at_least.
>	(_M_create_plus, _M_create_and_place): Declare.
>	(assign): Use _S_allocate_at_least.
>	* include/bits/basic_string.tcc (_M_create_plus): Define.
>	(_M_create_and_place): Define, abstracting common operations.
>	(_M_replace, reserve): Use _S_allocate_at_least.
>	(_M_construct, input iterators): Use _M_create_plus.
>	(_M_construct, others (3x)): Use _M_create_and_place.
>	(_M_create, _M_assign, reserve, _M_mutate): Use _M_create_plus.
>	* include/bits/memory_resource.h (allocate_at_least): Define,
>	document.
>	* include/bits/memoryfwd.h (__allocate_to_alignment): Declare.
>	* include/bits/new_allocator.h (allocate_at_least): Define, relying
>	on __allocate_to_alignment from alloc_traits.h.
>	(deallocate): Refine "if constexpr" logic.
>	* include/bits/stl_vector.h:
>	(_S_max_size): Move to _Vector_base.
>	(_Alloc_result): Define type.
>	(_M_allocate_at_least): Define, using allocate_at_least where supported.
>	(_M_allocate): Delegate to _M_allocate_at_least.
>	(max_size, _S_check_init_len): Use _S_max_size as moved.
>	(_M_create_storage, append_range, _M_allocate_and_copy,
>	_M_displace_storage): Define, abstracting common operations.
>	(_M_allocate_and_migrate): Define, likewise.
>	(_M_range_initialize_n): Use _M_allocate_at_least.
>	(_M_check_len): Improve logic.
>	* include/bits/vector.tcc:
>	(reserve, _M_fill_append, _M_range_insert): Use _M_allocate_at_least
>	and _M_displace_storage.
>	(operator=, _M_assign_aux): Use _M_allocate_and_migrate.
>	(_M_realloc_insert, _M_realloc_append, _M_default_append, insert_range):
>	Use _M_allocate_at_least.
>	(_M_fill_insert): Use _M_displace_storage, normalize whitespace.
>	* include/std/string: Define __glibcxx_want_allocate_at_least.
>	* include/std/vector: Same.
>	* testsuite/util/testsuite_allocator.h
>	(allocate_at_least (2x)): Define.
>	(allocate): Use allocate_at_least.
>	* testsuite/20_util/allocator/allocate_at_least.cc: Add tests.
>	* testsuite/21_strings/basic_string/capacity/char/18654.cc:
>	Loosen capacity check.
>	* testsuite/21_strings/basic_string/capacity/char/shrink_to_fit.cc:
>	Same.
>	* testsuite/21_strings/basic_string/capacity/wchar_t/18654.cc: Same.
>	* testsuite/21_strings/basic_string/capacity/wchar_t/2.cc: Same.
>	* testsuite/21_strings/basic_string/capacity/wchar_t/shrink_to_fit.cc:
>	Same.
>	* testsuite/23_containers/vector/capacity/shrink_to_fit.cc: Same.
>	* testsuite/23_containers/vector/capacity/shrink_to_fit2.cc: Same
>	* testsuite/23_containers/vector/modifiers/emplace/self_emplace.cc:
>	Adapt to looser reserve behavior.
>	* config/abi/pre/gnu.ver: Expose string::_S_allocate_at_least,
>	_M_create_plus, and _M_create_and_place symbols.
>---
> libstdc++-v3/config/abi/pre/gnu.ver           |   5 +
> libstdc++-v3/include/bits/alloc_traits.h      |  62 ++++++-
> libstdc++-v3/include/bits/allocator.h         |  28 +++-
> libstdc++-v3/include/bits/basic_string.h      |  38 ++++-
> libstdc++-v3/include/bits/basic_string.tcc    | 108 +++++++-----
> libstdc++-v3/include/bits/memory_resource.h   |  25 +++
> libstdc++-v3/include/bits/memoryfwd.h         |   6 +-
> libstdc++-v3/include/bits/new_allocator.h     |  41 ++++-
> libstdc++-v3/include/bits/stl_vector.h        | 132 +++++++++++----
> libstdc++-v3/include/bits/vector.tcc          | 155 +++++++-----------
> libstdc++-v3/include/std/string               |   1 +
> libstdc++-v3/include/std/vector               |   1 +
> .../20_util/allocator/allocate_at_least.cc    | 108 +++++++++++-
> .../basic_string/capacity/char/18654.cc       |   7 +-
> .../capacity/char/shrink_to_fit.cc            |   7 +-
> .../basic_string/capacity/wchar_t/18654.cc    |   8 +-
> .../basic_string/capacity/wchar_t/2.cc        |  13 +-
> .../capacity/wchar_t/shrink_to_fit.cc         |   7 +-
> .../vector/capacity/shrink_to_fit.cc          |   7 +-
> .../vector/capacity/shrink_to_fit2.cc         |  14 +-
> .../vector/modifiers/emplace/self_emplace.cc  |  36 ++--
> .../testsuite/util/testsuite_allocator.h      |  35 ++++
> 22 files changed, 614 insertions(+), 230 deletions(-)
>
>diff --git a/libstdc++-v3/config/abi/pre/gnu.ver b/libstdc++-v3/config/abi/pre/gnu.ver
>index 624de951d41..bfb710cc9bc 100644
>--- a/libstdc++-v3/config/abi/pre/gnu.ver
>+++ b/libstdc++-v3/config/abi/pre/gnu.ver
>@@ -2606,6 +2606,11 @@ GLIBCXX_3.4.35 {
>     _ZNSbIwSt11char_traitsIwESaIwEEC[12]EvQ26is_default_constructible_vIT1_E;
>     _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EEC[1-2]EvQ26is_default_constructible_vIT1_E;
>
>+    # std::basic_string:_S_allocate_at_least
>+    _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE*_S_allocate_*;
>+    _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE*_M_create_*;

This patch should waiot for GCC 17 now, so the new symbols will need
to be in a new symbol version, GLIBCXX_3.4.36. We can deal with that
after the 16.1.0 release.

>+
>+
> #if defined (_WIN32) && !defined (__CYGWIN__)
>     _ZSt19__get_once_callablev;
>     _ZSt15__get_once_callv;
>diff --git a/libstdc++-v3/include/bits/alloc_traits.h b/libstdc++-v3/include/bits/alloc_traits.h
>index 2be8ed561d4..3ded07eba3c 100644
>--- a/libstdc++-v3/include/bits/alloc_traits.h
>+++ b/libstdc++-v3/include/bits/alloc_traits.h
>@@ -42,6 +42,9 @@
> #  include <bits/stl_iterator.h> // __make_move_if_noexcept_iterator
> # endif
> #endif
>+#ifdef __glibcxx_allocate_at_least  // C++23
>+# include <bit>  // for rotl
>+#endif
>
> namespace std _GLIBCXX_VISIBILITY(default)
> {
>@@ -419,10 +422,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       */
>       [[nodiscard]] static constexpr auto
>       allocate_at_least(_Alloc& __a, size_type __n)
>-	-> allocation_result<pointer, size_type>
>+      -> allocation_result<pointer, size_type>
>       {
> 	if constexpr (requires { __a.allocate_at_least(__n); })
>-	  return __a.allocate_at_least(__n);
>+	  {
>+	    // Destructure to enable implicit __p -> pointer conversion.

Why is any conversion needed here?

allocator_traits::pointer has to be the same type as the allocator
returns.

>+	    auto [__p, __c] = __a.allocate_at_least(__n);
>+	    return { __p, __c };
>+	  }
> 	else
> 	  return { __a.allocate(__n), __n };
>       }
>@@ -664,15 +671,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        *  @brief  Allocate memory, generously.
>        *  @param  __a  An allocator.
>        *  @param  __n  The minimum number of objects to allocate space for.
>-       *  @return Memory of suitable size and alignment for `n` or more
>-       *  contiguous objects of type `value_type`.
>+       *  @return Memory of suitable size and alignment for `m >= n`
>+       *  contiguous objects of type `value_type`, and `m`.
>        *
>-       *  Returns `a.allocate_at_least(n)`.
>+       *  Returns `a.allocate_at_least(n)` if that is well-formed,
>+       *  or `{ a.allocate(n), n } otherwise.
>       */
>-      [[nodiscard]] static constexpr auto
>-      allocate_at_least(allocator_type __a, size_type __n)
>-	-> allocation_result<pointer, size_type>
>-      { return __a.allocate_at_least(__n); }
>+      [[nodiscard,__gnu__::__always_inline__]] static constexpr auto

A newline after the attributes please

>+      allocate_at_least(allocator_type& __a, size_type __n)
>+      -> allocation_result<pointer, size_type>
>+      {
>+	if constexpr (requires
>+	       { __a.allocate_at_least(__n); })

Do we need this condition?

std::allocator guarantees that the member is available. A
program-defined specialization of std::allocator needs to provide it
to be valid in C++26.

>+	  return __a.allocate_at_least(__n);
>+	else
>+	  return { __a.allocate(__n), __n };
>+      }
> #endif
>
>       /**
>@@ -822,6 +836,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       static void*
>       allocate(allocator_type&, size_type, const void* = nullptr) = delete;
>
>+#ifdef __glibcxx_allocate_at_least
>+      static allocation_result<pointer, size_type>
>+      allocate_at_least(allocator_type&, size_type) = delete;
>+#endif
>+
>       /// deallocate is ill-formed for allocator<void>
>       static void
>       deallocate(allocator_type&, void*, size_type) = delete;
>@@ -874,6 +893,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>     };
> #endif
>
>+#ifdef __glibcxx_allocate_at_least
>+  template <typename _Pointer, typename _Size, typename _Alloc_fn>
>+    [[nodiscard]] constexpr auto
>+    __allocate_to_alignment(_Size __n, _Alloc_fn&& __af)

This function is used in exactly one place, and is specific to
operator new because of the use of __STDCPP_DEFAULT_NEW_ALIGNMENT__ so
I think it would be better to just inline this code into
__new_allocator::allocate_at_least, or make this a private static
member function of __new_allocator.

That way we know that n * objsize won't overflow (because we know that
it's already been checked by __new_allocator, whereas here it's
callable by any arbitrary code which might not have checked that).

I also don't like passing in the allocation function, that seems ugly
and potentially an optimization barrier.

>+    -> allocation_result<_Pointer, _Size>
>+    {
>+      const _Size __align_mask = __STDCPP_DEFAULT_NEW_ALIGNMENT__ - 1;
>+      const _Size __objsize = sizeof(*declval<_Pointer>());
>+      if constexpr (__objsize > __align_mask)
>+	return { __af(__n * __objsize), __n };
>+      else
>+	{
>+	  const _Size __need = __n * __objsize;
>+	  _Size __ask = (__need + __align_mask) & ~__align_mask;
>+	  // Avoid rounding up to and asking for 2^63 bytes (PR108377):
>+	  __ask -= rotl(__ask, 1) & 1;

Does this optimize as well as:

     __ask -= bool(__ask & (_Size(1) << (__SIZE_WIDTH__ - 1)));

?

>+	  using _U8 = const unsigned char;
>+	  static_assert(__objsize <= ~_U8());
>+	  // Use 8-bit division:

Why?

>+	  _U8 __spare = __ask - __need, __size = __objsize;
>+	  return { __af(__ask), __n + __spare / __size };
>+	}
>+    }
>+#endif
>+
>   /// @cond undocumented
> #pragma GCC diagnostic push
> #pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
>diff --git a/libstdc++-v3/include/bits/allocator.h b/libstdc++-v3/include/bits/allocator.h
>index 9c22c805ebe..790edfaba41 100644
>--- a/libstdc++-v3/include/bits/allocator.h
>+++ b/libstdc++-v3/include/bits/allocator.h
>@@ -193,9 +193,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       constexpr _Tp*
>       allocate(size_t __n)
>       {
>-#if __cpp_concepts
>+# if __cpp_concepts
> 	if constexpr (requires { sizeof(_Tp); })
>-#endif
>+# endif
> 	if (std::__is_constant_evaluated())
> 	  {
> 	    if (__builtin_mul_overflow(__n, sizeof(_Tp), &__n))
>@@ -205,7 +205,25 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
> 	return __allocator_base<_Tp>::allocate(__n, 0);
>       }
>+#endif
>+
>+#ifdef __glibcxx_allocate_at_least  // C++23
>+      [[nodiscard,__gnu__::__always_inline__]]
>+      constexpr auto
>+      allocate_at_least(size_t __n)
>+      -> allocation_result<_Tp*, size_t>
>+      {
>+	if (std::is_constant_evaluated())

It's preferable to use if consteval in code that doesn't need to
compile as <= C++20. It is less work for the compiler.

>+	  return { allocate(__n), __n };
>+	else if constexpr (requires
>+	       { __allocator_base<_Tp>::allocate_at_least(__n); })
>+	  return __allocator_base<_Tp>::allocate_at_least(__n);
>+	else
>+	  return { __allocator_base<_Tp>::allocate(__n, 0), __n };

You don't need to pass a hint argument to allocate
(we might remove the second parameter some day, since the two-argument
form of std::allocator::allocate was deprecated and removed).

>+      }
>+#endif
>
>+#if __cpp_constexpr_dynamic_alloc // >= C++20
>       [[__gnu__::__always_inline__]]
>       constexpr void
>       deallocate(_Tp* __p, size_t __n)
>@@ -219,12 +237,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       }
> #endif // C++20
>
>-#ifdef __glibcxx_allocate_at_least  // C++23
>-      [[nodiscard]] constexpr allocation_result<_Tp*, size_t>
>-      allocate_at_least(size_t __n)
>-      { return { this->allocate(__n), __n }; }
>-#endif
>-
>       friend __attribute__((__always_inline__)) _GLIBCXX20_CONSTEXPR
>       bool
>       operator==(const allocator&, const allocator&) _GLIBCXX_NOTHROW
>diff --git a/libstdc++-v3/include/bits/basic_string.h b/libstdc++-v3/include/bits/basic_string.h
>index af4e5d9486f..98bffa50f32 100644
>--- a/libstdc++-v3/include/bits/basic_string.h
>+++ b/libstdc++-v3/include/bits/basic_string.h
>@@ -135,20 +135,35 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
> #endif
>
>     private:
>+      // For ABI reasons this must remain, though unused in active code.
>       static _GLIBCXX20_CONSTEXPR pointer
>       _S_allocate(_Char_alloc_type& __a, size_type __n)
>+      { return _S_allocate_at_least(__a, __n).__ptr; }

This means that all callers of this function in objects compiled with
older versions of GCC will now execute the extra instructions in
allocate_at_least to round up the size, and then ignore it. That goes
against "you don't pay for what you don't use".

It seems better to keep the old behaviour of this function.

>+
>+      struct _Alloc_result { pointer __ptr; size_type __count; };
>+
>+      static _GLIBCXX20_CONSTEXPR _Alloc_result
>+      _S_allocate_at_least(_Char_alloc_type& __a, size_type __n)
>       {
>-	pointer __p = _Alloc_traits::allocate(__a, __n);
>+	_Alloc_result __r;
>+#ifdef __glibcxx_allocate_at_least  // C++23
>+	auto [__ptr, __count] = _Alloc_traits::allocate_at_least(__a, __n);
>+	__r.__ptr = __ptr;
>+	__r.__count = __count;
>+#else
>+	__r.__ptr = _Alloc_traits::allocate(__a, __n);
>+	__r.__count = __n;
>+#endif
> #if __glibcxx_constexpr_string >= 201907L
> 	// std::char_traits begins the lifetime of characters,
> 	// but custom traits might not, so do it here.
> 	if constexpr (!is_same_v<_Traits, char_traits<_CharT>>)
> 	  if (std::__is_constant_evaluated())
> 	    // Begin the lifetime of characters in allocated storage.
>-	    for (size_type __i = 0; __i < __n; ++__i)
>-	      std::construct_at(__builtin_addressof(__p[__i]));
>+	    for (size_type __i = 0; __i < __r.__count; ++__i)
>+	      std::construct_at(__builtin_addressof(__r.__ptr[__i]));
> #endif
>-	return __p;
>+	return __r;
>       }
>
> #ifdef __glibcxx_string_view // >= C++17
>@@ -287,6 +302,15 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
>
>       // Create & Destroy
>       _GLIBCXX20_CONSTEXPR
>+      _Alloc_result
>+      _M_create_plus(size_type __new_capacity, size_type __old_capacity);
>+
>+      _GLIBCXX20_CONSTEXPR
>+      void
>+      _M_create_and_place(size_type __new_capacity, size_type __old_capacity);
>+
>+      // This must remain for ABI stability though unused.
>+      _GLIBCXX20_CONSTEXPR
>       pointer
>       _M_create(size_type&, size_type);
>
>@@ -1782,10 +1806,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
> 		    const auto __len = __str.size();
> 		    auto __alloc = __str._M_get_allocator();
> 		    // If this allocation throws there are no effects:
>-		    auto __ptr = _S_allocate(__alloc, __len + 1);
>+		    auto __r = _S_allocate_at_least(__alloc, __len + 1);
> 		    _M_destroy(_M_allocated_capacity);
>-		    _M_data(__ptr);
>-		    _M_capacity(__len);
>+		    _M_data(__r.__ptr);
>+		    _M_capacity(__r.__count - 1);
> 		    _M_set_length(__len);
> 		  }
> 	      }
>diff --git a/libstdc++-v3/include/bits/basic_string.tcc b/libstdc++-v3/include/bits/basic_string.tcc
>index b00dd550237..aaf8df26b1a 100644
>--- a/libstdc++-v3/include/bits/basic_string.tcc
>+++ b/libstdc++-v3/include/bits/basic_string.tcc
>@@ -139,14 +139,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
>   template<typename _CharT, typename _Traits, typename _Alloc>
>     _GLIBCXX20_CONSTEXPR
>-    typename basic_string<_CharT, _Traits, _Alloc>::pointer
>+    typename basic_string<_CharT, _Traits, _Alloc>::_Alloc_result
>     basic_string<_CharT, _Traits, _Alloc>::
>-    _M_create(size_type& __capacity, size_type __old_capacity)
>+    _M_create_plus(size_type __capacity, size_type __old_capacity)
>     {
>       // _GLIBCXX_RESOLVE_LIB_DEFECTS
>       // 83.  String::npos vs. string::max_size()
>       if (__capacity > max_size())
>-	std::__throw_length_error(__N("basic_string::_M_create"));
>+	std::__throw_length_error(__N("basic_string::_M_create_plus"));
>
>       // The below implements an exponential growth policy, necessary to
>       // meet amortized linear time requirements of the library: see
>@@ -161,7 +161,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
>       // NB: Need an array of char_type[__capacity], plus a terminating
>       // null char_type() element.
>-      return _S_allocate(_M_get_allocator(), __capacity + 1);
>+      return _S_allocate_at_least(_M_get_allocator(), __capacity + 1);
>+    }
>+
>+
>+  template<typename _CharT, typename _Traits, typename _Alloc>
>+    _GLIBCXX20_CONSTEXPR
>+    void
>+    basic_string<_CharT, _Traits, _Alloc>::
>+    _M_create_and_place(size_type __capacity, size_type __old_capacity)
>+    {
>+      _Alloc_result __r = _M_create_plus(__capacity, __old_capacity);
>+      _M_data(__r.__ptr);
>+      _M_capacity(__r.__count - 1);  // Leave room for NUL.
>+    }
>+
>+  // This must remain for ABI stability, though unused in current code.
>+  template<typename _CharT, typename _Traits, typename _Alloc>
>+    _GLIBCXX20_CONSTEXPR
>+    typename basic_string<_CharT, _Traits, _Alloc>::pointer
>+    basic_string<_CharT, _Traits, _Alloc>::
>+    _M_create(size_type& __capacity, size_type __old_capacity)
>+    {
>+      _Alloc_result __r = _M_create_plus(__capacity, __old_capacity);

Again, this makes existing callers of _M_create perform more work,
although in this case they will actually use the extra capacity, so
it's not wasted work.

>+      __capacity = __r.__count - 1;  // Leave room for NUL.
>+      return __r.__ptr;
>     }
>
>   // NB: This is the special case for Input Iterators, used in
>@@ -203,11 +227,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> 	    if (__len == __capacity)
> 	      {
> 		// Allocate more space.
>-		__capacity = __len + 1;
>-		pointer __another = _M_create(__capacity, __len);
>-		this->_S_copy(__another, _M_data(), __len);
>+		_Alloc_result __another = _M_create_plus(__len + 1, __len);
>+		__capacity = __another.__count - 1; // Leave room for NUL.
>+		this->_S_copy(__another.__ptr, _M_data(), __len);
> 		_M_dispose();
>-		_M_data(__another);
>+		_M_data(__another.__ptr);
> 		_M_capacity(__capacity);
> 	      }
> 	    traits_type::assign(_M_data()[__len++],
>@@ -231,10 +255,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> 	size_type __dnew = static_cast<size_type>(std::distance(__beg, __end));
>
> 	if (__dnew > size_type(_S_local_capacity))
>-	  {
>-	    _M_data(_M_create(__dnew, size_type(0)));
>-	    _M_capacity(__dnew);
>-	  }
>+	  _M_create_and_place(__dnew, size_type(0));
> 	else
> 	  _M_init_local_buf();
>
>@@ -264,10 +285,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>     _M_construct(size_type __n, _CharT __c)
>     {
>       if (__n > size_type(_S_local_capacity))
>-	{
>-	  _M_data(_M_create(__n, size_type(0)));
>-	  _M_capacity(__n);
>-	}
>+	_M_create_and_place(__n, size_type(0));
>       else
> 	_M_init_local_buf();
>
>@@ -281,16 +299,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>   // than difference between iterators.
>   template<typename _CharT, typename _Traits, typename _Alloc>
>     template<bool _Terminated>
>-    _GLIBCXX20_CONSTEXPR
>+    _GLIBCXX20_CONSTEXPR
>     void
>     basic_string<_CharT, _Traits, _Alloc>::
>     _M_construct(const _CharT* __str, size_type __n)
>     {
>       if (__n > size_type(_S_local_capacity))
>-	{
>-	  _M_data(_M_create(__n, size_type(0)));
>-	  _M_capacity(__n);
>-	}
>+	_M_create_and_place(__n, size_type(0));
>       else
> 	_M_init_local_buf();
>
>@@ -347,11 +362,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
> 	  if (__rsize > __capacity)
> 	    {
>-	      size_type __new_capacity = __rsize;
>-	      pointer __tmp = _M_create(__new_capacity, __capacity);
>+	      // if _M_create_plus throws, there is no effect.
>+	      _Alloc_result __tmp = _M_create_plus(__rsize, __capacity);
> 	      _M_dispose();
>-	      _M_data(__tmp);
>-	      _M_capacity(__new_capacity);
>+	      _M_data(__tmp.__ptr);
>+	      _M_capacity(__tmp.__count - 1);
> 	    }
>
> 	  if (__rsize)
>@@ -375,11 +390,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       if (__res <= __capacity)
> 	return;
>
>-      pointer __tmp = _M_create(__res, __capacity);
>-      this->_S_copy(__tmp, _M_data(), length() + 1);
>+      _Alloc_result __r = _M_create_plus(__res, __capacity);
>+      this->_S_copy(__r.__ptr, _M_data(), length() + 1);
>       _M_dispose();
>-      _M_data(__tmp);
>-      _M_capacity(__res);
>+      _M_data(__r.__ptr);
>+      _M_capacity(__r.__count - 1);  // Leave room for NUL.
>     }
>
>   template<typename _CharT, typename _Traits, typename _Alloc>
>@@ -392,19 +407,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       const size_type __how_much = length() - __pos - __len1;
>
>       size_type __new_capacity = length() + __len2 - __len1;
>-      pointer __r = _M_create(__new_capacity, capacity());
>+      _Alloc_result __r = _M_create_plus(__new_capacity, capacity());
>
>       if (__pos)
>-	this->_S_copy(__r, _M_data(), __pos);
>+	this->_S_copy(__r.__ptr, _M_data(), __pos);
>       if (__s && __len2)
>-	this->_S_copy(__r + __pos, __s, __len2);
>+	this->_S_copy(__r.__ptr + __pos, __s, __len2);
>       if (__how_much)
>-	this->_S_copy(__r + __pos + __len2,
>+	this->_S_copy(__r.__ptr + __pos + __len2,
> 		      _M_data() + __pos + __len1, __how_much);
>
>       _M_dispose();
>-      _M_data(__r);
>-      _M_capacity(__new_capacity);
>+      _M_data(__r.__ptr);
>+      _M_capacity(__r.__count - 1);  // Leave room for NUL.
>     }
>
>   template<typename _CharT, typename _Traits, typename _Alloc>
>@@ -430,6 +445,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       if (_M_is_local())
> 	return;
>
>+# ifdef __glibcxx_allocate_at_least  // C++23
>+      const size_type __limit = (__STDCPP_DEFAULT_NEW_ALIGNMENT__ - 1) / sizeof(_CharT);
>+# else
>+      const size_type __limit = 0;
>+# endif
>       const size_type __length = length();
>       const size_type __capacity = _M_allocated_capacity;
>
>@@ -441,14 +461,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> 	  _M_data(_M_local_data());
> 	}
> #if __cpp_exceptions
>-      else if (__length < __capacity)
>+      else if (__capacity - __length > __limit )
> 	try
> 	  {
>-	    pointer __tmp = _S_allocate(_M_get_allocator(), __length + 1);
>-	    this->_S_copy(__tmp, _M_data(), __length + 1);
>+	    _Alloc_result __r = _S_allocate_at_least(
>+	      _M_get_allocator(), __length + 1);
>+	    this->_S_copy(__r.__ptr, _M_data(), __length + 1);
> 	    _M_dispose();
>-	    _M_data(__tmp);
>-	    _M_capacity(__length);
>+	    _M_data(__r.__ptr);
>+	    _M_capacity(__r.__count - 1);  // reserve room for NUL.
> 	  }
> 	catch (const __cxxabiv1::__forced_unwind&)
> 	  { throw; }
>@@ -588,7 +609,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> #if __cpp_lib_is_constant_evaluated
> 	  if (std::is_constant_evaluated())
> 	    {
>-	      auto __newp = _S_allocate(_M_get_allocator(), __new_size);
>+	      auto __newp =
>+		_S_allocate_at_least(_M_get_allocator(), __new_size).__ptr;

If this branch isn't going to use the excess capacity, why bother
using _S_allocate_at_least? _S_allocate seems simpler here.

> 	      _S_copy(__newp, this->_M_data(), __pos);
> 	      _S_copy(__newp + __pos, __s, __len2);
> 	      _S_copy(__newp + __pos + __len2, __p + __len1, __how_much);
>@@ -677,7 +699,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> #endif // C++11
>
> #endif  // _GLIBCXX_USE_CXX11_ABI
>-
>+
> #if __glibcxx_constexpr_string >= 201907L
> # define _GLIBCXX_STRING_CONSTEXPR constexpr
> #else
>@@ -916,7 +938,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> 	      // Avoid reallocation for common case.
> 	      __str.erase();
> 	      _CharT __buf[128];
>-	      __size_type __len = 0;	
>+	      __size_type __len = 0;
> 	      const streamsize __w = __in.width();
> 	      const __size_type __n = __w > 0 ? static_cast<__size_type>(__w)
> 		                              : __str.max_size();
>diff --git a/libstdc++-v3/include/bits/memory_resource.h b/libstdc++-v3/include/bits/memory_resource.h
>index e5c6697b07e..0a0f799437b 100644
>--- a/libstdc++-v3/include/bits/memory_resource.h
>+++ b/libstdc++-v3/include/bits/memory_resource.h
>@@ -48,6 +48,7 @@
> # include <bits/utility.h>		// index_sequence
> # include <tuple>			// tuple, forward_as_tuple
> #endif
>+#include <bits/memoryfwd.h>
>
> namespace std _GLIBCXX_VISIBILITY(default)
> {
>@@ -468,6 +469,30 @@ namespace pmr
>       allocate(allocator_type& __a, size_type __n, const_void_pointer)
>       { return __a.allocate(__n); }
>
>+#ifdef __glibcxx_allocate_at_least
>+      /**
>+       *  @brief  Allocate memory, generously.
>+       *  @param  __a  An allocator.
>+       *  @param  __n  The number of objects to allocate space for.
>+       *  @return Memory of suitable size and alignment for `m >= n`
>+       *          objects of type `value_type`, and `m`.
>+       *
>+       *  Returns `a.allocate_at_least(n)` if it exists, otherwise
>+       *  `a.allocate(n)`.
>+      */
>+      [[nodiscard]] static auto
>+      allocate_at_least(allocator_type& __a, size_type __n)
>+      -> std::allocation_result<pointer, size_type>
>+      {
>+	if constexpr (requires { __a.allocate_at_least(__n); })

pmr::polymorphic_allocator doesn't have allocate_at_least so this
condition is never true.

We do want allocate_at_least in this allocator_traits partial
specialization but it should just return { __a.allocate(__n), __n}
unconditionally.

>+	  {
>+	    auto [__p, __c] =    __a.allocate_at_least(__n);
>+	    return { __p, __c };  // Enable conversion en route.
>+	  }
>+	else return { __a.allocate(__n), __n };
>+      }
>+#endif
>+
>       /**
>        *  @brief  Deallocate memory.
>        *  @param  __a  An allocator.
>diff --git a/libstdc++-v3/include/bits/memoryfwd.h b/libstdc++-v3/include/bits/memoryfwd.h
>index 9efe72b38ba..6742f75499e 100644
>--- a/libstdc++-v3/include/bits/memoryfwd.h
>+++ b/libstdc++-v3/include/bits/memoryfwd.h
>@@ -88,8 +88,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       _Pointer ptr;
>       _Size count;
>     };
>-#endif
>
>+  template <typename _Pointer, typename _Size, typename _Alloc_fn>
>+    [[nodiscard]] constexpr auto
>+    __allocate_to_alignment(_Size __n, _Alloc_fn&& __af)
>+    -> allocation_result<_Pointer, _Size>;
>+#endif
>
>   /// @} group memory
>
>diff --git a/libstdc++-v3/include/bits/new_allocator.h b/libstdc++-v3/include/bits/new_allocator.h
>index fbe03e392aa..9a3f245f2aa 100644
>--- a/libstdc++-v3/include/bits/new_allocator.h
>+++ b/libstdc++-v3/include/bits/new_allocator.h
>@@ -37,6 +37,7 @@
> #if __cplusplus >= 201103L
> #include <type_traits>
> #endif
>+#include <bits/memoryfwd.h>
>
> namespace std _GLIBCXX_VISIBILITY(default)
> {
>@@ -150,6 +151,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> 	      std::__throw_bad_array_new_length();
> 	    std::__throw_bad_alloc();
> 	  }
>+
> #if __cpp_aligned_new && __cplusplus >= 201103L
> 	else if constexpr (alignof(_Tp) > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
> 	  {
>@@ -162,6 +164,41 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> 	  return static_cast<_Tp*>(_GLIBCXX_OPERATOR_NEW(__n * sizeof(_Tp)));
>       }
>
>+#ifdef __glibcxx_allocate_at_least  // C++23
>+      [[nodiscard]] constexpr auto
>+      allocate_at_least(size_t __n)
>+      -> std::allocation_result<_Tp*, size_t>
>+      {
>+	static_assert(requires { sizeof(_Tp); },
>+	  "cannot allocate incomplete types");
>+
>+	if constexpr (!requires { sizeof(_Tp); })
>+	  return { nullptr, 0 }; // static_assert already failed
>+	else if (__builtin_expect(__n > this->_M_max_size(), false))
>+	  {
>+	    // _GLIBCXX_RESOLVE_LIB_DEFECTS
>+	    // 3190. allocator::allocate sometimes returns too little storage
>+	    if (__n > (std::size_t(-1) / sizeof(_Tp)))
>+	      std::__throw_bad_array_new_length();
>+	    std::__throw_bad_alloc();
>+	  }
>+	else if (std::__is_constant_evaluated())

This should be 'if consteval'

>+	  {
>+	    auto __p = _GLIBCXX_OPERATOR_NEW(__n * sizeof(_Tp));
>+	    return { static_cast<_Tp*>(__p), __n };
>+	  }
>+	else if constexpr (alignof(_Tp) > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
>+	  {
>+	    const std::align_val_t __al = std::align_val_t(alignof(_Tp));
>+	    auto __p = _GLIBCXX_OPERATOR_NEW(__n * sizeof(_Tp), __al);
>+	    return { static_cast<_Tp*>(__p), __n };
>+	  }
>+	else
>+	  return __allocate_to_alignment<_Tp*>(__n, [](size_t __bytes)
>+	   { return static_cast<_Tp*>(_GLIBCXX_OPERATOR_NEW(__bytes)); });
>+      }
>+#endif
>+
>       // __p is not permitted to be a null pointer.
>       _GLIBCXX20_CONSTEXPR void
>       deallocate(_Tp* __p, size_type __n __attribute__ ((__unused__)))
>@@ -177,10 +214,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> 	  {
> 	    _GLIBCXX_OPERATOR_DELETE(_GLIBCXX_SIZED_DEALLOC(__p, __n),
> 				     std::align_val_t(alignof(_Tp)));
>-	    return;
> 	  }
>+	else
> #endif
>-	_GLIBCXX_OPERATOR_DELETE(_GLIBCXX_SIZED_DEALLOC(__p, __n));
>+	  _GLIBCXX_OPERATOR_DELETE(_GLIBCXX_SIZED_DEALLOC(__p, __n));
>       }
>
> #pragma GCC diagnostic pop
>diff --git a/libstdc++-v3/include/bits/stl_vector.h b/libstdc++-v3/include/bits/stl_vector.h
>index c4ca214752a..4efe3d0f9d2 100644
>--- a/libstdc++-v3/include/bits/stl_vector.h
>+++ b/libstdc++-v3/include/bits/stl_vector.h
>@@ -317,6 +317,19 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>       get_allocator() const _GLIBCXX_NOEXCEPT
>       { return allocator_type(_M_get_Tp_allocator()); }
>
>+      static _GLIBCXX20_CONSTEXPR size_t
>+      _S_max_size(const _Tp_alloc_type& __a) _GLIBCXX_NOEXCEPT
>+      {
>+	// std::distance(begin(), end()) cannot be greater than PTRDIFF_MAX,
>+	// and realistically we can't store more than PTRDIFF_MAX/sizeof(T)
>+	// (even if std::allocator_traits::max_size says we can).
>+	const size_t __diffmax =
>+	  __gnu_cxx::__numeric_traits<ptrdiff_t>::__max / sizeof(_Tp);
>+	const size_t __allocmax =
>+	  __gnu_cxx::__alloc_traits<_Alloc>::max_size(__a);
>+	return (std::min)(__diffmax, __allocmax);
>+      }
>+
> #if __cplusplus >= 201103L
>       _Vector_base() = default;
> #else
>@@ -384,9 +397,37 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>       _GLIBCXX20_CONSTEXPR
>       pointer
>       _M_allocate(size_t __n)
>+      { return _M_allocate_at_least(__n).__ptr; }

This also does the rounding up work, then ignores it.

>+
>+      struct _Alloc_result { pointer __ptr; size_t __count; };
>+
>+      _GLIBCXX20_CONSTEXPR
>+      _Alloc_result
>+      _M_allocate_at_least(size_t __n)
>       {
> 	typedef __gnu_cxx::__alloc_traits<_Tp_alloc_type> _Tr;
>-	return __n != 0 ? _Tr::allocate(_M_impl, __n) : pointer();
>+	_Alloc_result __r;
>+	if (__builtin_expect(__n != 0, true))
>+	  {
>+#ifdef __glibcxx_allocate_at_least  // C++23
>+	    if constexpr (
>+		requires(_Tp_alloc_type& __a, size_t __n) { _Tr::allocate_at_least(__a, __n); })
>+	      {
>+		auto [__ptr, __count] = _Tr::allocate_at_least(_M_impl, __n);
>+		if (__count > __n)
>+		  {
>+		    size_t __max = _S_max_size(_M_get_Tp_allocator());
>+		    if (__builtin_expect(__count > __max, false))
>+		      __count = __max;
>+		  }
>+		__r.__ptr = __ptr, __r.__count = __count;

Separate statements for the assignments please.

>+	      }
>+	    else
>+#endif
>+	      __r.__ptr = _Tr::allocate(_M_impl, __n), __r.__count = __n;
>+	  }
>+	else __r.__ptr = pointer(), __r.__count = 0;

And again, although this line could be avoided by initializing __r
using {}.

>+	return __r;
>       }
>
>       _GLIBCXX20_CONSTEXPR
>@@ -404,9 +445,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>       void
>       _M_create_storage(size_t __n)
>       {
>-	this->_M_impl._M_start = this->_M_allocate(__n);
>-	this->_M_impl._M_finish = this->_M_impl._M_start;
>-	this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
>+	_Alloc_result __r = this->_M_allocate_at_least(__n);
>+	this->_M_impl._M_finish = this->_M_impl._M_start = __r.__ptr;
>+	this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __r.__count;
>       }
>
> #if __glibcxx_containers_ranges // C++ >= 23
>@@ -480,6 +521,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>       typedef _Vector_base<_Tp, _Alloc>			_Base;
>       typedef typename _Base::_Tp_alloc_type		_Tp_alloc_type;
>       typedef __gnu_cxx::__alloc_traits<_Tp_alloc_type>	_Alloc_traits;
>+      typedef typename _Base::_Alloc_result             _Alloc_result;
>
>     public:
>       typedef _Tp					value_type;
>@@ -536,6 +578,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>
>     protected:
>       using _Base::_M_allocate;
>+      using _Base::_M_allocate_at_least;
>       using _Base::_M_deallocate;
>       using _Base::_M_impl;
>       using _Base::_M_get_Tp_allocator;
>@@ -1116,7 +1159,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>       _GLIBCXX_NODISCARD _GLIBCXX20_CONSTEXPR
>       size_type
>       max_size() const _GLIBCXX_NOEXCEPT
>-      { return _S_max_size(_M_get_Tp_allocator()); }
>+      { return _Base::_S_max_size(_M_get_Tp_allocator()); }
>
> #if __cplusplus >= 201103L
>       /**
>@@ -1682,16 +1725,17 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 		  return;
> 		}
>
>-	      const size_type __len = _M_check_len(__n, "vector::append_range");
>+	      const size_type __ask = _M_check_len(__n, "vector::append_range");
>
> 	      pointer __old_start = this->_M_impl._M_start;
> 	      pointer __old_finish = this->_M_impl._M_finish;
>
>-	      allocator_type& __a = _M_get_Tp_allocator();
>-	      const pointer __start = this->_M_allocate(__len);
>+	      auto [__ptr, __got] = this->_M_allocate_at_least(__ask);
>+	      const pointer __start = __ptr;
> 	      const pointer __mid = __start + __sz;
> 	      const pointer __back = __mid + __n;
>-	      _Guard_alloc __guard(__start, __len, *this);
>+	      _Guard_alloc __guard(__start, __got, *this);
>+	      allocator_type& __a = _M_get_Tp_allocator();
> 	      std::__uninitialized_copy_a(ranges::begin(__rg),
> 					  ranges::end(__rg),
> 					  __mid, __a);
>@@ -1733,7 +1777,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 	      // Finally, take ownership of new storage:
> 	      this->_M_impl._M_start = __start;
> 	      this->_M_impl._M_finish = __back;
>-	      this->_M_impl._M_end_of_storage = __start + __len;
>+	      this->_M_impl._M_end_of_storage = __start + __got;
> 	    }
> 	  else
> 	    {
>@@ -1889,20 +1933,45 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>     protected:
>       /**
>        *  Memory expansion handler.  Uses the member allocation function to
>-       *  obtain @a n bytes of memory, and then copies [first,last) into it.
>+       *  obtain at least `n` objects worth of memory, copies `[first,last)`
>+       *  into it, reports what was actually allocated.
>        */
>       template<typename _ForwardIterator>
> 	_GLIBCXX20_CONSTEXPR
>-	pointer
>-	_M_allocate_and_copy(size_type __n,
>-			     _ForwardIterator __first, _ForwardIterator __last)
>+	_Alloc_result
>+	_M_allocate_and_copy(
>+	  size_type __n, _ForwardIterator __first, _ForwardIterator __last)

The parameter formatting was correct before this change.

> 	{
>-	  _Guard_alloc __guard(this->_M_allocate(__n), __n, *this);
>+	  _Alloc_result __r = this->_M_allocate_at_least(__n);
>+	  _Guard_alloc __guard(__r.__ptr, __r.__count, *this);
> 	  std::__uninitialized_copy_a
> 	    (__first, __last, __guard._M_storage, _M_get_Tp_allocator());
>-	  return __guard._M_release();
>+	  (void) __guard._M_release();
>+	  return __r;
> 	}
>
>+      _GLIBCXX20_CONSTEXPR void
>+      _M_displace_storage(pointer __start, pointer __end, size_type __cap)

Would _M_replace_storage be a better name?

"displace" has three meanings, one of which is to take the place of
something else (the intended meaning here, where the new storage
replaces the old), but another meaning is to just push something out
of its place. So at the call site, if I read "displace storage" I
don't know what it's doing.


>+      {
>+	  _GLIBCXX_ASAN_ANNOTATE_REINIT;
>+	  _M_deallocate(this->_M_impl._M_start,
>+	    this->_M_impl._M_end_of_storage - this->_M_impl._M_start);
>+	  this->_M_impl._M_start = __start;
>+	  this->_M_impl._M_finish = __end;
>+	  this->_M_impl._M_end_of_storage = __start + __cap;
>+      }
>+
>+      template<typename _ForwardIterator>
>+	_GLIBCXX20_CONSTEXPR
>+	void
>+	_M_allocate_and_migrate(

This name doesn't tell me what it does either, "migrate" doesn't tell
me that it makes a copy of [first, last) rather than migrating
something *to* that range.

Would _M_replace_with be better? The fact it allocates isn't really
the more pertinent thing about this function, what it does is replace
the current storage with new storage containing a copy of the range
[first, last).

>+	  size_type __n, _ForwardIterator __first, _ForwardIterator __last)

Incorrect formatting again.

>+	{
>+	  _Alloc_result __r = _M_allocate_and_copy(__n, __first, __last);
>+	  std::_Destroy(this->_M_impl._M_start,
>+	    this->_M_impl._M_finish, _M_get_Tp_allocator());
>+	  _M_displace_storage(__r.__ptr, __r.__ptr + __n, __r.__count);
>+	}
>
>       // Internal constructor functions follow.
>
>@@ -1971,10 +2040,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 	_M_range_initialize_n(_Iterator __first, _Sentinel __last,
> 			      size_type __n)
> 	{
>-	  pointer __start =
>-	    this->_M_allocate(_S_check_init_len(__n, _M_get_Tp_allocator()));
>+	  _Alloc_result __r = this->_M_allocate_at_least(
>+	    _S_check_init_len(__n, _M_get_Tp_allocator()));
>+	  pointer __start = __r.__ptr;
> 	  this->_M_impl._M_start = this->_M_impl._M_finish = __start;
>-	  this->_M_impl._M_end_of_storage = __start + __n;
>+	  this->_M_impl._M_end_of_storage = __start + __r.__count;
> 	  this->_M_impl._M_finish
> 	      = std::__uninitialized_copy_a(_GLIBCXX_MOVE(__first), __last,
> 					    __start, _M_get_Tp_allocator());
>@@ -2191,35 +2261,27 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>       size_type
>       _M_check_len(size_type __n, const char* __s) const
>       {
>-	if (max_size() - size() < __n)
>+	const size_type __room = max_size() - size();
>+	if (__room < __n)
> 	  __throw_length_error(__N(__s));
>
>-	const size_type __len = size() + (std::max)(size(), __n);
>-	return (__len < size() || __len > max_size()) ? max_size() : __len;
>+	if (__n < size())
>+	  __n = size();  // Grow by (at least) doubling ...
>+	if (__n > __room)
>+	  __n = __room;  //  ... but only as much as will fit.
>+	return size() + __n;
>       }
>
>       // Called by constructors to check initial size.
>       static _GLIBCXX20_CONSTEXPR size_type
>       _S_check_init_len(size_type __n, const allocator_type& __a)
>       {
>-	if (__n > _S_max_size(_Tp_alloc_type(__a)))
>+	if (__n > _Base::_S_max_size(_Tp_alloc_type(__a)))
> 	  __throw_length_error(
> 	      __N("cannot create std::vector larger than max_size()"));
> 	return __n;
>       }
>
>-      static _GLIBCXX20_CONSTEXPR size_type
>-      _S_max_size(const _Tp_alloc_type& __a) _GLIBCXX_NOEXCEPT
>-      {
>-	// std::distance(begin(), end()) cannot be greater than PTRDIFF_MAX,
>-	// and realistically we can't store more than PTRDIFF_MAX/sizeof(T)
>-	// (even if std::allocator_traits::max_size says we can).
>-	const size_t __diffmax
>-	  = __gnu_cxx::__numeric_traits<ptrdiff_t>::__max / sizeof(_Tp);
>-	const size_t __allocmax = _Alloc_traits::max_size(__a);
>-	return (std::min)(__diffmax, __allocmax);
>-      }
>-
>       // Internal erase functions follow.
>
>       // Called by erase(q1,q2), clear(), resize(), _M_fill_assign,
>diff --git a/libstdc++-v3/include/bits/vector.tcc b/libstdc++-v3/include/bits/vector.tcc
>index b790fca2964..86da54bdff1 100644
>--- a/libstdc++-v3/include/bits/vector.tcc
>+++ b/libstdc++-v3/include/bits/vector.tcc
>@@ -75,13 +75,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>       if (this->capacity() < __n)
> 	{
> 	  const size_type __old_size = size();
>-	  pointer __tmp;
>+	  _Alloc_result __tmp;
> #if __cplusplus >= 201103L
> 	  if constexpr (_S_use_relocate())
> 	    {
>-	      __tmp = this->_M_allocate(__n);
>+	      __tmp = this->_M_allocate_at_least(__n);
> 	      std::__relocate_a(this->_M_impl._M_start, this->_M_impl._M_finish,
>-				__tmp, _M_get_Tp_allocator());
>+				__tmp.__ptr, _M_get_Tp_allocator());
> 	    }
> 	  else
> #endif
>@@ -92,13 +92,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 	      std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
> 			    _M_get_Tp_allocator());
> 	    }
>-	  _GLIBCXX_ASAN_ANNOTATE_REINIT;
>-	  _M_deallocate(this->_M_impl._M_start,
>-			this->_M_impl._M_end_of_storage
>-			- this->_M_impl._M_start);
>-	  this->_M_impl._M_start = __tmp;
>-	  this->_M_impl._M_finish = __tmp + __old_size;
>-	  this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
>+	  _M_displace_storage(
>+	    __tmp.__ptr, __tmp.__ptr + __old_size, __tmp.__count);
> 	}
>     }
> #pragma GCC diagnostic pop
>@@ -106,12 +101,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> #if __cplusplus >= 201103L
>   template<typename _Tp, typename _Alloc>
>     template<typename... _Args>
>-#if __cplusplus > 201402L
>+# if __cplusplus > 201402L
>       _GLIBCXX20_CONSTEXPR
>       typename vector<_Tp, _Alloc>::reference
>-#else
>+# else
>       void
>-#endif
>+# endif
>       vector<_Tp, _Alloc>::
>       emplace_back(_Args&&... __args)
>       {
>@@ -125,11 +120,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 	  }
> 	else
> 	  _M_realloc_append(std::forward<_Args>(__args)...);
>-#if __cplusplus > 201402L
>+# if __cplusplus > 201402L
> 	return back();
>-#endif
>+# endif
>       }
>-#endif
>+#endif  // __cplusplus >= 201103L
>
>   template<typename _Tp, typename _Alloc>
>     _GLIBCXX20_CONSTEXPR
>@@ -238,22 +233,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> #endif
> 	  const size_type __xlen = __x.size();
> 	  if (__xlen > capacity())
>-	    {
>-	      pointer __tmp = _M_allocate_and_copy(__xlen, __x.begin(),
>-						   __x.end());
>-	      std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
>-			    _M_get_Tp_allocator());
>-	      _M_deallocate(this->_M_impl._M_start,
>-			    this->_M_impl._M_end_of_storage
>-			    - this->_M_impl._M_start);
>-	      this->_M_impl._M_start = __tmp;
>-	      this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __xlen;
>-	    }
>+	    _M_allocate_and_migrate(__xlen, __x.begin(), __x.end());
> 	  else if (size() >= __xlen)
>-	    {
>-	      std::_Destroy(std::copy(__x.begin(), __x.end(), begin()),
>-			    end(), _M_get_Tp_allocator());
>-	    }
>+	    std::_Destroy(std::copy(__x.begin(), __x.end(), begin()),
>+	      end(), _M_get_Tp_allocator());
> 	  else
> 	    {
> 	      std::copy(__x._M_impl._M_start, __x._M_impl._M_start + size(),
>@@ -332,16 +315,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 	      __builtin_unreachable();
>
> 	    _S_check_init_len(__len, _M_get_Tp_allocator());
>-	    pointer __tmp(_M_allocate_and_copy(__len, __first, __last));
>-	    std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
>-			  _M_get_Tp_allocator());
>-	    _GLIBCXX_ASAN_ANNOTATE_REINIT;
>-	    _M_deallocate(this->_M_impl._M_start,
>-			  this->_M_impl._M_end_of_storage
>-			  - this->_M_impl._M_start);
>-	    this->_M_impl._M_start = __tmp;
>-	    this->_M_impl._M_finish = this->_M_impl._M_start + __len;
>-	    this->_M_impl._M_end_of_storage = this->_M_impl._M_finish;
>+	    _M_allocate_and_migrate(__len, __first, __last);
> 	  }
> 	else if (__sz >= __len)
> 	  _M_erase_at_end(std::copy(__first, __last, this->_M_impl._M_start));
>@@ -464,13 +438,15 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>     _M_realloc_insert(iterator __position, const _Tp& __x)
> #endif
>     {
>-      const size_type __len = _M_check_len(1u, "vector::_M_realloc_insert");
>-      if (__len <= 0)
>+      const size_type __len1 = _M_check_len(1u, "vector::_M_realloc_insert");
>+      if (__len1 <= 0)
> 	__builtin_unreachable();
>       pointer __old_start = this->_M_impl._M_start;
>       pointer __old_finish = this->_M_impl._M_finish;
>       const size_type __elems_before = __position - begin();
>-      pointer __new_start(this->_M_allocate(__len));
>+      _Alloc_result __r = this->_M_allocate_at_least(__len1);
>+      const size_type __len = __r.__count;
>+      pointer __new_start(__r.__ptr);
>       pointer __new_finish(__new_start);
>
>       {
>@@ -574,14 +550,16 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>       const size_type __len = _M_check_len(1u, "vector::_M_realloc_append");
>       if (__len <= 0)
> 	__builtin_unreachable();
>-      pointer __old_start = this->_M_impl._M_start;
>-      pointer __old_finish = this->_M_impl._M_finish;
>+      const pointer __old_start = this->_M_impl._M_start;
>+      const pointer __old_finish = this->_M_impl._M_finish;
>       const size_type __elems = size();
>-      pointer __new_start(this->_M_allocate(__len));
>+      const _Alloc_result __r = this->_M_allocate_at_least(__len);
>+      const size_type __rlen = __r.__count;
>+      const pointer __new_start(__r.__ptr);
>       pointer __new_finish(__new_start);
>
>       {
>-	_Guard_alloc __guard(__new_start, __len, *this);
>+	_Guard_alloc __guard(__new_start, __rlen, *this);
>
> 	// The order of the three operations is dictated by the C++11
> 	// case, where the moves could alter a new element belonging
>@@ -652,7 +630,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>
>       this->_M_impl._M_start = __new_start;
>       this->_M_impl._M_finish = __new_finish;
>-      this->_M_impl._M_end_of_storage = __new_start + __len;
>+      this->_M_impl._M_end_of_storage = __new_start + __rlen;
>     }
> #pragma GCC diagnostic pop
>
>@@ -716,10 +694,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 	      pointer __old_finish = this->_M_impl._M_finish;
> 	      const pointer __pos = __position.base();
>
>-	      const size_type __len =
>+	      const size_type __len1 =
> 		_M_check_len(__n, "vector::_M_fill_insert");
> 	      const size_type __elems_before = __pos - __old_start;
>-	      pointer __new_start(this->_M_allocate(__len));
>+	      _Alloc_result __r = this->_M_allocate_at_least(__len1);
>+	      const size_type __len = __r.__count;
>+	      pointer __new_start(__r.__ptr);
> 	      pointer __new_finish(__new_start);
> 	      __try
> 		{
>@@ -727,21 +707,16 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 		  std::__uninitialized_fill_n_a(__new_start + __elems_before,
> 						__n, __x,
> 						_M_get_Tp_allocator());
>-		  __new_finish = pointer();
>-
>-		  __new_finish
>-		    = std::__uninitialized_move_if_noexcept_a
>-		    (__old_start, __pos, __new_start, _M_get_Tp_allocator());
>-
>+		  __new_finish = pointer();  // ... in case of a throw.
>+		  __new_finish = std::__uninitialized_move_if_noexcept_a(
>+		      __old_start, __pos, __new_start, _M_get_Tp_allocator());
> 		  __new_finish += __n;
>-
>-		  __new_finish
>-		    = std::__uninitialized_move_if_noexcept_a
>-		    (__pos, __old_finish, __new_finish, _M_get_Tp_allocator());
>+		  __new_finish = std::__uninitialized_move_if_noexcept_a(
>+		      __pos, __old_finish, __new_finish, _M_get_Tp_allocator());
> 		}
> 	      __catch(...)
> 		{
>-		  if (!__new_finish)
>+		  if (__new_finish == pointer())
> 		    std::_Destroy(__new_start + __elems_before,
> 				  __new_start + __elems_before + __n,
> 				  _M_get_Tp_allocator());
>@@ -752,12 +727,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 		  __throw_exception_again;
> 		}
> 	      std::_Destroy(__old_start, __old_finish, _M_get_Tp_allocator());
>-	      _GLIBCXX_ASAN_ANNOTATE_REINIT;
>-	      _M_deallocate(__old_start,
>-			    this->_M_impl._M_end_of_storage - __old_start);
>-	      this->_M_impl._M_start = __new_start;
>-	      this->_M_impl._M_finish = __new_finish;
>-	      this->_M_impl._M_end_of_storage = __new_start + __len;
>+	      _M_displace_storage(__new_start, __new_finish, __len);
> 	    }
> 	}
>     }
>@@ -785,9 +755,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 	   pointer __old_finish = this->_M_impl._M_finish;
> 	   const size_type __old_size = __old_finish - __old_start;
>
>-	   const size_type __len =
>-	     _M_check_len(__n, "vector::_M_fill_append");
>-	   pointer __new_start(this->_M_allocate(__len));
>+	   size_type __len = _M_check_len(__n, "vector::_M_fill_append");
>+	   _Alloc_result __r = this->_M_allocate_at_least(__len);
>+	   __len = __r.__count;
>+	   pointer __new_start(__r.__ptr);
> 	   pointer __new_finish(__new_start + __old_size);
> 	   __try
> 	     {
>@@ -807,12 +778,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 		__throw_exception_again;
> 	      }
> 	   std::_Destroy(__old_start, __old_finish, _M_get_Tp_allocator());
>-	   _GLIBCXX_ASAN_ANNOTATE_REINIT;
>-	   _M_deallocate(__old_start,
>-			 this->_M_impl._M_end_of_storage - __old_start);
>-	   this->_M_impl._M_start = __new_start;
>-	   this->_M_impl._M_finish = __new_finish;
>-	   this->_M_impl._M_end_of_storage = __new_start + __len;
>+	   _M_displace_storage(__new_start, __new_finish, __len);
> 	 }
>     }
>
>@@ -852,9 +818,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 	      pointer __old_start = this->_M_impl._M_start;
> 	      pointer __old_finish = this->_M_impl._M_finish;
>
>-	      const size_type __len =
>+	      const size_type __len1 =
> 		_M_check_len(__n, "vector::_M_default_append");
>-	      pointer __new_start(this->_M_allocate(__len));
>+	      _Alloc_result __r = this->_M_allocate_at_least(__len1);
>+	      const size_type __len = __r.__count;
>+	      pointer __new_start(__r.__ptr);
>
> 	      {
> 		_Guard_alloc __guard(__new_start, __len, *this);
>@@ -1003,14 +971,16 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 		pointer __old_start = this->_M_impl._M_start;
> 		pointer __old_finish = this->_M_impl._M_finish;
>
>-		const size_type __len =
>+		const size_type __ask =
> 		  _M_check_len(__n, "vector::_M_range_insert");
> #if __cplusplus < 201103L
>-		if (__len < (__n + (__old_finish - __old_start)))
>+		if (__ask < (__n + (__old_finish - __old_start)))
> 		  __builtin_unreachable();
> #endif
>
>-		pointer __new_start(this->_M_allocate(__len));
>+		_Alloc_result __r = this->_M_allocate_at_least(__ask);
>+		const size_type __got = __r.__count;
>+		pointer __new_start(__r.__ptr);
> 		pointer __new_finish(__new_start);
> 		__try
> 		  {
>@@ -1031,17 +1001,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 		  {
> 		    std::_Destroy(__new_start, __new_finish,
> 				  _M_get_Tp_allocator());
>-		    _M_deallocate(__new_start, __len);
>+		    _M_deallocate(__new_start, __got);
> 		    __throw_exception_again;
> 		  }
> 		std::_Destroy(__old_start, __old_finish,
> 			      _M_get_Tp_allocator());
>-		_GLIBCXX_ASAN_ANNOTATE_REINIT;
>-		_M_deallocate(__old_start,
>-			      this->_M_impl._M_end_of_storage - __old_start);
>-		this->_M_impl._M_start = __new_start;
>-		this->_M_impl._M_finish = __new_finish;
>-		this->_M_impl._M_end_of_storage = __new_start + __len;
>+		_M_displace_storage(__new_start, __new_finish, __got);
> 	      }
> 	  }
>       }
>@@ -1111,7 +1076,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 	      }
> 	    else // Reallocate
> 	      {
>-		const size_type __len
>+		const size_type __ask
> 		  = _M_check_len(__n, "vector::insert_range");
>
> 		struct _Guard : _Guard_alloc
>@@ -1130,8 +1095,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 		};
>
> 		// Allocate new storage:
>-		pointer __new_start(this->_M_allocate(__len));
>-		_Guard __guard(__new_start, __len, *this);
>+		_Alloc_result __r = this->_M_allocate_at_least(__ask);
>+		const size_type __got = __r.__count;
>+		pointer __new_start(__r.__ptr);
>+		_Guard __guard(__new_start, __got, *this);
>
> 		auto& __alloc = _M_get_Tp_allocator();
>
>@@ -1158,13 +1125,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> 		// with __guard so that it cleans up the old storage:
> 		this->_M_impl._M_start = __guard._M_storage;
> 		this->_M_impl._M_finish = __guard._M_finish;
>-		this->_M_impl._M_end_of_storage = __new_start + __len;
>+		this->_M_impl._M_end_of_storage = __new_start + __got;
> 		__guard._M_storage = __old_start;
> 		__guard._M_finish = __old_finish;
> 		__guard._M_len = (__old_finish - __old_start) + __cap;
> 		// _Asan::_Reinit destructor marks unused capacity.
> 		// _Guard destructor destroys [old_start,old_finish).
>-		// _Guard_alloc destructor frees [old_start,old_start+len).
>+		// _Guard_alloc destructor frees [old_start,old_start+got).
> 	      }
> 	    return begin() + __ins_idx;
> 	  }
>diff --git a/libstdc++-v3/include/std/string b/libstdc++-v3/include/std/string
>index c2b37391fc7..da50bce2e7d 100644
>--- a/libstdc++-v3/include/std/string
>+++ b/libstdc++-v3/include/std/string
>@@ -67,6 +67,7 @@
> #endif
>
> #define __glibcxx_want_algorithm_default_value_type
>+#define __glibcxx_want_allocate_at_least

This macro should only defined in <memory> and <version>, not here.

> #define __glibcxx_want_allocator_traits_is_always_equal
> #define __glibcxx_want_constexpr_char_traits
> #define __glibcxx_want_constexpr_string
>diff --git a/libstdc++-v3/include/std/vector b/libstdc++-v3/include/std/vector
>index 343483e9519..ac041cc9408 100644
>--- a/libstdc++-v3/include/std/vector
>+++ b/libstdc++-v3/include/std/vector
>@@ -79,6 +79,7 @@
> #endif
>
> #define __glibcxx_want_algorithm_default_value_type
>+#define __glibcxx_want_allocate_at_least

And not here either.

> #define __glibcxx_want_allocator_traits_is_always_equal
> #define __glibcxx_want_constexpr_vector
> #define __glibcxx_want_containers_ranges
>diff --git a/libstdc++-v3/testsuite/20_util/allocator/allocate_at_least.cc b/libstdc++-v3/testsuite/20_util/allocator/allocate_at_least.cc
>index 5399096d294..987e2472490 100644
>--- a/libstdc++-v3/testsuite/20_util/allocator/allocate_at_least.cc
>+++ b/libstdc++-v3/testsuite/20_util/allocator/allocate_at_least.cc
>@@ -29,7 +29,7 @@ template <typename T>
>       }
>   };
>
>-int main()
>+void base()
> {
>   std::allocator<X> native;
>   auto a1 = native.allocate_at_least(100);
>@@ -63,3 +63,109 @@ int main()
>   VERIFY(a5.ptr == minimal.keep);
>   minimal_traits::deallocate(minimal, a5.ptr, a5.count);
> }
>+
>+void extra()
>+{
>+  using SatC = std::allocator_traits<std::allocator<char>>;
>+  std::allocator<char> satc;
>+  {
>+    auto [p, n] = SatC::allocate_at_least(satc, 1);
>+    VERIFY(n == __STDCPP_DEFAULT_NEW_ALIGNMENT__);
>+    SatC::deallocate(satc, p, n);
>+  }
>+  {
>+    auto [p, n] = SatC::allocate_at_least(satc, 2);
>+    VERIFY(n == __STDCPP_DEFAULT_NEW_ALIGNMENT__);
>+    SatC::deallocate(satc, p, n);
>+  }
>+  {
>+    auto [p, n] =
>+      SatC::allocate_at_least(satc, __STDCPP_DEFAULT_NEW_ALIGNMENT__ - 1);
>+    VERIFY(n == __STDCPP_DEFAULT_NEW_ALIGNMENT__);
>+    SatC::deallocate(satc, p, n);
>+  }
>+  {
>+    auto [p, n] = SatC::allocate_at_least(
>+	satc, __STDCPP_DEFAULT_NEW_ALIGNMENT__);
>+    VERIFY(n == __STDCPP_DEFAULT_NEW_ALIGNMENT__);
>+    SatC::deallocate(satc, p, n);
>+  }
>+
>+  using SatS = std::allocator_traits<std::allocator<short>>;
>+  std::allocator<short> sats;
>+  {
>+    auto [p, n] = SatS::allocate_at_least(sats, 1);
>+    VERIFY(n == __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(short));
>+    SatS::deallocate(sats, p, n);
>+  }
>+  {
>+    auto [p, n] = SatS::allocate_at_least(sats, 2);
>+    VERIFY(n == __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(short));
>+    SatS::deallocate(sats, p, n);
>+  }
>+  {
>+    auto [p, n] = SatS::allocate_at_least(sats,
>+	(__STDCPP_DEFAULT_NEW_ALIGNMENT__ - 1) / sizeof(short));
>+    VERIFY(n == __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(short));
>+    SatS::deallocate(sats, p, n);
>+  }
>+  {
>+    auto [p, n] = SatS::allocate_at_least(sats,
>+	__STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(short));
>+    VERIFY(n == __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(short));
>+    SatS::deallocate(sats, p, n);
>+  }
>+
>+  struct A3 { char s[3]; };
>+  using SatA3 = std::allocator_traits<std::allocator<A3>>;
>+  std::allocator<A3> sata3;
>+  {
>+    auto [p, n] = SatA3::allocate_at_least(sata3, 1);
>+    VERIFY(n == __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(A3));
>+    SatA3::deallocate(sata3, p, n);
>+  }
>+  {
>+    auto [p, n] = SatA3::allocate_at_least(sata3, 2);
>+    VERIFY(n == __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(A3));
>+    SatA3::deallocate(sata3, p, n);
>+  }
>+  {
>+    auto [p, n] = SatA3::allocate_at_least(sata3,
>+	(__STDCPP_DEFAULT_NEW_ALIGNMENT__ - 1) / sizeof(A3));
>+    VERIFY(n == __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(A3));
>+    SatA3::deallocate(sata3, p, n);
>+  }
>+  {
>+    auto [p, n] = SatA3::allocate_at_least(sata3,
>+	__STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(A3));
>+    VERIFY(n == __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(A3));
>+    SatA3::deallocate(sata3, p, n);
>+  }
>+
>+  struct Anm1 { char s[__STDCPP_DEFAULT_NEW_ALIGNMENT__ - 1]; };
>+  using SatAnm1 = std::allocator_traits<std::allocator<Anm1>>;
>+  std::allocator<Anm1> satanm1;
>+  {
>+    auto [p, n] = SatAnm1::allocate_at_least(satanm1, 1);
>+    VERIFY(n == 1);
>+    SatAnm1::deallocate(satanm1, p, n);
>+  }
>+  {
>+    auto [p, n] = SatAnm1::allocate_at_least(satanm1,
>+	__STDCPP_DEFAULT_NEW_ALIGNMENT__);
>+    VERIFY(n == __STDCPP_DEFAULT_NEW_ALIGNMENT__);
>+    SatAnm1::deallocate(satanm1, p, n);
>+  }
>+  {
>+    auto [p, n] = SatAnm1::allocate_at_least(satanm1,
>+	__STDCPP_DEFAULT_NEW_ALIGNMENT__ - 1);
>+    VERIFY(n == __STDCPP_DEFAULT_NEW_ALIGNMENT__);
>+    SatAnm1::deallocate(satanm1, p, n);
>+  }
>+}
>+
>+int main()
>+{
>+  base();
>+  extra();
>+}
>diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/capacity/char/18654.cc b/libstdc++-v3/testsuite/21_strings/basic_string/capacity/char/18654.cc
>index d542f34d08e..59012a1c170 100644
>--- a/libstdc++-v3/testsuite/21_strings/basic_string/capacity/char/18654.cc
>+++ b/libstdc++-v3/testsuite/21_strings/basic_string/capacity/char/18654.cc
>@@ -58,7 +58,12 @@ void test01()
> #else
>       str.shrink_to_fit(); // reserve is deprecated in C++20
> #endif
>-      VERIFY( str.capacity() == i );
>+#if __glibcxx_allocate_at_least
>+      unsigned limit = __STDCPP_DEFAULT_NEW_ALIGNMENT__ - 1;
>+#else
>+      unsigned limit = 0;
>+#endif
>+      VERIFY( str.capacity() - i <= limit);
>     }
> }
>
>diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/capacity/char/shrink_to_fit.cc b/libstdc++-v3/testsuite/21_strings/basic_string/capacity/char/shrink_to_fit.cc
>index a26d524dddf..ae5df746f35 100644
>--- a/libstdc++-v3/testsuite/21_strings/basic_string/capacity/char/shrink_to_fit.cc
>+++ b/libstdc++-v3/testsuite/21_strings/basic_string/capacity/char/shrink_to_fit.cc
>@@ -30,7 +30,12 @@ void test01()
>   s.push_back('b');
>   VERIFY( s.size() < s.capacity() );
>   s.shrink_to_fit();
>-  VERIFY( s.size() == s.capacity() );
>+#ifdef __glibcxx_allocate_at_least
>+  unsigned limit = __STDCPP_DEFAULT_NEW_ALIGNMENT__ - 1;
>+#else
>+  unsigned limit = 0;
>+#endif
>+  VERIFY( s.capacity() - s.size() <= limit );
> }
>
> int main()
>diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/capacity/wchar_t/18654.cc b/libstdc++-v3/testsuite/21_strings/basic_string/capacity/wchar_t/18654.cc
>index 49e45c764c4..bf3b8dd5581 100644
>--- a/libstdc++-v3/testsuite/21_strings/basic_string/capacity/wchar_t/18654.cc
>+++ b/libstdc++-v3/testsuite/21_strings/basic_string/capacity/wchar_t/18654.cc
>@@ -50,6 +50,7 @@ void test01()
>       const size_type cap = str.capacity();
>       VERIFY( cap >= 3 * i );
>
>+      // no shrink.
>       str.reserve(2 * i);
>       VERIFY( str.capacity() == cap );
>
>@@ -58,7 +59,12 @@ void test01()
> #else
>       str.shrink_to_fit(); // reserve is deprecated in C++20
> #endif
>-      VERIFY( str.capacity() == i );
>+#if __glibcxx_allocate_at_least
>+      unsigned limit = __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(wchar_t) - 1;
>+#else
>+      unsigned limit = 0;
>+#endif
>+      VERIFY( str.capacity() - i <= limit);
>     }
> }
>
>diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/capacity/wchar_t/2.cc b/libstdc++-v3/testsuite/21_strings/basic_string/capacity/wchar_t/2.cc
>index bff2bdd1862..04a3763fc77 100644
>--- a/libstdc++-v3/testsuite/21_strings/basic_string/capacity/wchar_t/2.cc
>+++ b/libstdc++-v3/testsuite/21_strings/basic_string/capacity/wchar_t/2.cc
>@@ -27,14 +27,21 @@
> void test02()
> {
>   std::wstring str01 = L"twelve chars";
>-  // str01 becomes shared
>-  std::wstring str02 = str01;
>+  str01.reserve(100);
> #if __cplusplus <= 201703L
>   str01.reserve();
> #else
>   str01.shrink_to_fit(); // reserve is deprecated in C++20
> #endif
>-  VERIFY( str01.capacity() == 12 );
>+  // These are not guaranteed to absolutely minimize storage.
>+  // allocator<wchar_t>::allocate_at_least rounds up to what
>+  // it knows ::op new delivers.
>+#ifdef __glibcxx_allocate_at_least
>+  unsigned limit = __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(wchar_t) - 1;
>+#else
>+  unsigned limit = 0;
>+#endif
>+  VERIFY( str01.capacity() - str01.size() <= limit);
> }
>
> int main()
>diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/capacity/wchar_t/shrink_to_fit.cc b/libstdc++-v3/testsuite/21_strings/basic_string/capacity/wchar_t/shrink_to_fit.cc
>index b4d0224a9d8..fc0f659539f 100644
>--- a/libstdc++-v3/testsuite/21_strings/basic_string/capacity/wchar_t/shrink_to_fit.cc
>+++ b/libstdc++-v3/testsuite/21_strings/basic_string/capacity/wchar_t/shrink_to_fit.cc
>@@ -30,7 +30,12 @@ void test01()
>   s.push_back(L'b');
>   VERIFY( s.size() < s.capacity() );
>   s.shrink_to_fit();
>-  VERIFY( s.size() == s.capacity() );
>+#ifdef __glibcxx_allocate_at_least
>+  unsigned limit = __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(wchar_t) - 1;
>+#else
>+  unsigned limit = 0;
>+#endif
>+  VERIFY( s.capacity() - s.size() <= limit );
> }
>
> int main()
>diff --git a/libstdc++-v3/testsuite/23_containers/vector/capacity/shrink_to_fit.cc b/libstdc++-v3/testsuite/23_containers/vector/capacity/shrink_to_fit.cc
>index 74c68712b47..d4aa4d89b56 100644
>--- a/libstdc++-v3/testsuite/23_containers/vector/capacity/shrink_to_fit.cc
>+++ b/libstdc++-v3/testsuite/23_containers/vector/capacity/shrink_to_fit.cc
>@@ -30,11 +30,12 @@ void test01()
>   v.push_back(1);
>   VERIFY( v.size() < v.capacity() );
>   v.shrink_to_fit();
>-#if __cpp_exceptions
>-  VERIFY( v.size() == v.capacity() );
>+#ifdef __glibcxx_allocate_at_least
>+  unsigned limit = __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(int);
> #else
>-  VERIFY( v.size() < v.capacity() );
>+  unsigned limit = 0;
> #endif
>+  VERIFY(v.capacity() - v.size() <= limit);
> }
>
> int main()
>diff --git a/libstdc++-v3/testsuite/23_containers/vector/capacity/shrink_to_fit2.cc b/libstdc++-v3/testsuite/23_containers/vector/capacity/shrink_to_fit2.cc
>index c8faa9ded80..9489e043845 100644
>--- a/libstdc++-v3/testsuite/23_containers/vector/capacity/shrink_to_fit2.cc
>+++ b/libstdc++-v3/testsuite/23_containers/vector/capacity/shrink_to_fit2.cc
>@@ -32,7 +32,12 @@ void test01()
>   v.reserve(100);
>   VERIFY( v.size() < v.capacity() );
>   v.shrink_to_fit();
>-  VERIFY( v.size() == v.capacity() );
>+#ifdef __glibcxx_allocate_at_least
>+  unsigned limit = __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(int);
>+#else
>+  unsigned limit = 0;
>+#endif
>+  VERIFY( v.capacity() - v.size() <= limit);
>   VERIFY( v.get_allocator().get_personality() == alloc.get_personality() );
> }
>
>@@ -45,7 +50,12 @@ void test02()
>   v.reserve(100);
>   VERIFY( v.size() < v.capacity() );
>   v.shrink_to_fit();
>-  VERIFY( v.size() == v.capacity() );
>+#ifdef __glibcxx_allocate_at_least
>+  unsigned limit = __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(int);
>+#else
>+  unsigned limit = 0;
>+#endif
>+  VERIFY( v.capacity() - v.size() <= limit);
>   VERIFY( v.get_allocator().get_personality() == alloc.get_personality() );
> }
>
>diff --git a/libstdc++-v3/testsuite/23_containers/vector/modifiers/emplace/self_emplace.cc b/libstdc++-v3/testsuite/23_containers/vector/modifiers/emplace/self_emplace.cc
>index 00a0c7b06ea..629f35f05ba 100644
>--- a/libstdc++-v3/testsuite/23_containers/vector/modifiers/emplace/self_emplace.cc
>+++ b/libstdc++-v3/testsuite/23_containers/vector/modifiers/emplace/self_emplace.cc
>@@ -99,37 +99,37 @@ struct A
> void
> test03()
> {
>-  std::vector<A> va =
>-    {
>-      { A(1) },
>-      { A(2) },
>-      { A(3) }
>-    };
>-
>-  // Make sure emplace will imply reallocation.
>-  VERIFY( va.capacity() == 3 );
>+#ifdef __glibcxx_allocate_at_least
>+  unsigned fit = __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(A);
>+#else
>+  unsigned fit = 4;
>+#endif
>+  std::vector<A> va; va.reserve(fit);
>+  for (int i = 1; va.size() < va.capacity(); ++i)
>+      va.push_back(A(i));
>
>   va.emplace(va.begin(), va.begin());
>
>-  VERIFY( va.size() == 4 );
>+  VERIFY( va.size() == fit + 1 );
>   VERIFY( va[0]._i == 1 );
> }
>
> void
> test04()
> {
>-  std::vector<A> va =
>-    {
>-      { A(1) },
>-      { A(2) },
>-      { A(3) }
>-    };
>+#ifdef __glibcxx_allocate_at_least
>+  unsigned fit = __STDCPP_DEFAULT_NEW_ALIGNMENT__ / sizeof(A);
>+#else
>+  unsigned fit = 4;
>+#endif
>+  std::vector<A> va; va.reserve(fit);
>+  for (int i = 1; va.size() < va.capacity() - 1; ++i)
>+      va.push_back(A(i));
>
>   // Make sure emplace won't reallocate.
>-  va.reserve(4);
>   va.emplace(va.begin(), va.begin());
>
>-  VERIFY( va.size() == 4 );
>+  VERIFY( va.size() == fit );
>   VERIFY( va[0]._i == 1 );
> }
>
>diff --git a/libstdc++-v3/testsuite/util/testsuite_allocator.h b/libstdc++-v3/testsuite/util/testsuite_allocator.h
>index 892a385e307..a12099354ca 100644
>--- a/libstdc++-v3/testsuite/util/testsuite_allocator.h
>+++ b/libstdc++-v3/testsuite/util/testsuite_allocator.h
>@@ -168,6 +168,16 @@ namespace __gnu_test
> 	  : Alloc(alloc)
> 	{ }
>
>+#ifdef __glibcxx_allocate_at_least // C++23
>+      std::allocation_result<pointer, size_type>
>+      allocate_at_least(size_type n)
>+      {
>+	auto [p, c] = AllocTraits::allocate_at_least(*this, n);
>+	counter_type::allocate(c * sizeof(T));
>+	return { p, c };
>+      }
>+#endif
>+
>       pointer
>       allocate(size_type n, const void* = 0)
>       {
>@@ -373,6 +383,31 @@ namespace __gnu_test
> 	return p;
>       }
>
>+#ifdef __glibcxx_allocate_at_least
>+      constexpr auto
>+      allocate_at_least(size_type n)
>+      -> std::allocation_result<Tp*, size_t>
>+      {
>+	auto r = AllocTraits::allocate_at_least(*this, n);
>+
>+	if (std::__is_constant_evaluated())

if consteval

>+	  return r;
>+
>+	try
>+	  {
>+	    get_map().insert(map_type::value_type(
>+		  reinterpret_cast<void*>(r.ptr), personality));

Huh, why do we use reinterpret_cast here when an implicit conversion
or static_cast<void*> would work fine. But this is consistent with the
existing code, so that's a question for another day.

>+	  }
>+	catch(...)
>+	  {
>+	    AllocTraits::deallocate(*this, r.ptr, r.count);
>+	    __throw_exception_again;
>+	  }
>+
>+	return r;
>+      }
>+#endif
>+
>       _GLIBCXX14_CONSTEXPR
>       void
>       deallocate(pointer p, size_type n)
>-- 
>2.53.0
>
>



More information about the Libstdc++ mailing list