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

Message ID 20260404050329.949729-1-ncm@cantrip.org
State New
Headers
Series [RFC] libstdc++: Use allocate_at_least in vector, string (P0401) [PR118030] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 warning Skipped because it is an RFC
linaro-tcwg-bot/tcwg_gcc_build--master-arm warning Skipped because it is an RFC

Commit Message

Nathan Myers April 4, 2026, 4:52 a.m. UTC
  - Implement as much of allocator<>::allocate_at_least as is
  possible relying solely on known alignment behavior of
  standard operator new.
 - Use allocator_at_least in string and vector to maximize usage
  of actually allocated storage, so far as is known.

Defines and uses {string,vector}::_Alloc_result for all c++-*,
to reduce churn, actually calling allocate_at_least only where
defined. User-defined allocators can implement larger benefits,
and string and vector will take full advantage.

To do: Add tests; Fix constexpr test failures; By detecting
whether ::operator new has been replaced, we may rely on details 
of our allocator implementation.

libstdc++-v3/ChangeLog:
	PR libstdc++/118030
	* include/bits/alloc_traits.h (allocate_at_least): Call
	__a.allocate_at_least only if it exists.
	* include/bits/allocator.h (allocate_at_least): Remove.
	* include/bits/basic_string.h (_Alloc_result): Define.
	(_S_allocate): Delete.
	(_S_allocate_at_least): Define.
	(assign): Use _S_allocate_at_least.
	* include/bits/basic_string.tcc (_M_create, reserve, _M_replace):
	Use _S_allocate_at_least.
	* include/bits/memory_resource.h (allocate_at_least): Define.
	* include/bits/new_allocator.h (allocate_at_least): Define.
	* include/bits/stl_vector.h (_S_max_size): Move to _Vector_base.
	(_M_allocate): Remove.
	(_M_allocate_at_least): Define.
	(_Alloc_result): Define.
	(max_size, _S_check_init_len): Use _S_max_size as moved.
	(_M_create_storage, append_range, _M_allocate_and_copy,
	_M_initialize_dispatch, _M_range_initialize): Use _M_allocate_at_least.
	(_M_check_len): Improve logic.
	* include/bits/vector.tcc (reserve, _M_realloc_insert,
	_M_realloc_append, _M_fill_insert, _M_fill_append, _M_default_append,
	_M_range_insert, insert_range): Use _M_allocate_at_least.
	* include/std/string: Define __glibcxx_want_allocate_at_least.
	* include/std/vector: Same.
	* testsuite/21_strings/basic_string/cons/wchar_t/constexpr.cc:
	* testsuite/util/testsuite_allocator.h (allocate_at_least (2x)): Define.
	(allocate): Use allocate_at_least.
---
 libstdc++-v3/include/bits/alloc_traits.h      |  9 +-
 libstdc++-v3/include/bits/allocator.h         |  6 --
 libstdc++-v3/include/bits/basic_string.h      | 24 +++--
 libstdc++-v3/include/bits/basic_string.tcc    | 17 ++--
 libstdc++-v3/include/bits/memory_resource.h   | 16 ++++
 libstdc++-v3/include/bits/new_allocator.h     | 43 +++++++++
 libstdc++-v3/include/bits/stl_vector.h        | 95 ++++++++++++-------
 libstdc++-v3/include/bits/vector.tcc          | 66 ++++++++-----
 libstdc++-v3/include/std/string               |  1 +
 libstdc++-v3/include/std/vector               |  1 +
 .../basic_string/cons/wchar_t/constexpr.cc    |  2 +-
 .../testsuite/util/testsuite_allocator.h      | 40 ++++++++
 12 files changed, 239 insertions(+), 81 deletions(-)
  

Comments

Jonathan Wakely April 4, 2026, 7:14 a.m. UTC | #1
On Sat, 4 Apr 2026, 06:03 Nathan Myers, <ncm@cantrip.org> wrote:

>  - Implement as much of allocator<>::allocate_at_least as is
>   possible relying solely on known alignment behavior of
>   standard operator new.
>  - Use allocator_at_least in string and vector to maximize usage
>   of actually allocated storage, so far as is known.
>
> Defines and uses {string,vector}::_Alloc_result for all c++-*,
> to reduce churn, actually calling allocate_at_least only where
> defined. User-defined allocators can implement larger benefits,
> and string and vector will take full advantage.
>
> To do: Add tests; Fix constexpr test failures; By detecting
> whether ::operator new has been replaced, we may rely on details
> of our allocator implementation.
>
> libstdc++-v3/ChangeLog:
>         PR libstdc++/118030
>         * include/bits/alloc_traits.h (allocate_at_least): Call
>         __a.allocate_at_least only if it exists.
>         * include/bits/allocator.h (allocate_at_least): Remove.
>         * include/bits/basic_string.h (_Alloc_result): Define.
>         (_S_allocate): Delete.
>         (_S_allocate_at_least): Define.
>         (assign): Use _S_allocate_at_least.
>         * include/bits/basic_string.tcc (_M_create, reserve, _M_replace):
>         Use _S_allocate_at_least.
>         * include/bits/memory_resource.h (allocate_at_least): Define.
>         * include/bits/new_allocator.h (allocate_at_least): Define.
>         * include/bits/stl_vector.h (_S_max_size): Move to _Vector_base.
>         (_M_allocate): Remove.
>         (_M_allocate_at_least): Define.
>         (_Alloc_result): Define.
>         (max_size, _S_check_init_len): Use _S_max_size as moved.
>         (_M_create_storage, append_range, _M_allocate_and_copy,
>         _M_initialize_dispatch, _M_range_initialize): Use
> _M_allocate_at_least.
>         (_M_check_len): Improve logic.
>         * include/bits/vector.tcc (reserve, _M_realloc_insert,
>         _M_realloc_append, _M_fill_insert, _M_fill_append,
> _M_default_append,
>         _M_range_insert, insert_range): Use _M_allocate_at_least.
>         * include/std/string: Define __glibcxx_want_allocate_at_least.
>         * include/std/vector: Same.
>         * testsuite/21_strings/basic_string/cons/wchar_t/constexpr.cc:
>         * testsuite/util/testsuite_allocator.h (allocate_at_least (2x)):
> Define.
>         (allocate): Use allocate_at_least.
> ---
>  libstdc++-v3/include/bits/alloc_traits.h      |  9 +-
>  libstdc++-v3/include/bits/allocator.h         |  6 --
>  libstdc++-v3/include/bits/basic_string.h      | 24 +++--
>  libstdc++-v3/include/bits/basic_string.tcc    | 17 ++--
>  libstdc++-v3/include/bits/memory_resource.h   | 16 ++++
>  libstdc++-v3/include/bits/new_allocator.h     | 43 +++++++++
>  libstdc++-v3/include/bits/stl_vector.h        | 95 ++++++++++++-------
>  libstdc++-v3/include/bits/vector.tcc          | 66 ++++++++-----
>  libstdc++-v3/include/std/string               |  1 +
>  libstdc++-v3/include/std/vector               |  1 +
>  .../basic_string/cons/wchar_t/constexpr.cc    |  2 +-
>  .../testsuite/util/testsuite_allocator.h      | 40 ++++++++
>  12 files changed, 239 insertions(+), 81 deletions(-)
>
> diff --git a/libstdc++-v3/include/bits/alloc_traits.h
> b/libstdc++-v3/include/bits/alloc_traits.h
> index 2be8ed561d4..b773be365bc 100644
> --- a/libstdc++-v3/include/bits/alloc_traits.h
> +++ b/libstdc++-v3/include/bits/alloc_traits.h
> @@ -419,7 +419,7 @@ _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);
> @@ -672,7 +672,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        [[nodiscard]] static constexpr auto
>        allocate_at_least(allocator_type __a, size_type __n)
>         -> allocation_result<pointer, size_type>
> -      { return __a.allocate_at_least(__n); }
> +      {
> +       if constexpr(requires { __a.__allocate_at_least(__n); })
> +         return __a.allocate_at_least(__n);
> +       else
> +         return { __a.allocate(__n), __n };
> +      }
>  #endif
>
>        /**
> diff --git a/libstdc++-v3/include/bits/allocator.h
> b/libstdc++-v3/include/bits/allocator.h
> index 9c22c805ebe..ec160f44a1e 100644
> --- a/libstdc++-v3/include/bits/allocator.h
> +++ b/libstdc++-v3/include/bits/allocator.h
> @@ -219,12 +219,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
>


The standard requires this to exist, so if you move it to the base class
you need to implement it in all the possible base classes:
malloc_allocator, bitmap_allocator, etc.

Alternatively, keep it here, but use a requires expression to see if it's
present in the base class, and either call base::allocate_at_least or
base::allocate.


-
>        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 202a911f9ef..09a87d27910 100644
> --- a/libstdc++-v3/include/bits/basic_string.h
> +++ b/libstdc++-v3/include/bits/basic_string.h
> @@ -135,10 +135,18 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
>  #endif
>
>      private:
> -      static _GLIBCXX20_CONSTEXPR pointer
> -      _S_allocate(_Char_alloc_type& __a, size_type __n)
>

Removing this function will break ABI.

I think it needs to be kept, even if it is no longer used.


+      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.
> @@ -146,9 +154,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
>           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]));
> +             std::construct_at(__builtin_addressof(__r.__ptr[__i]));
>

Do we need to begin the lifetime of all characters up to r.count, not only
up to n?


 #endif
> -       return __p;
> +       return __r;
>        }
>
>  #ifdef __glibcxx_string_view // >= C++17
> @@ -1782,10 +1790,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);
>                     _M_set_length(__len);
>                   }
>               }
> diff --git a/libstdc++-v3/include/bits/basic_string.tcc
> b/libstdc++-v3/include/bits/basic_string.tcc
> index a223edf67ac..c56b214a3fd 100644
> --- a/libstdc++-v3/include/bits/basic_string.tcc
> +++ b/libstdc++-v3/include/bits/basic_string.tcc
> @@ -161,7 +161,10 @@ _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);
> +      _Alloc_result __r = _S_allocate_at_least(
> +       _M_get_allocator(), __capacity + 1);
> +      __capacity = __r.__count - 1;
> +      return __r.__ptr;
>      }
>
>    // NB: This is the special case for Input Iterators, used in
> @@ -444,11 +447,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        else if (__length < __capacity)
>         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 +592,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;
>               _S_copy(__newp, this->_M_data(), __pos);
>               _S_copy(__newp + __pos, __s, __len2);
>               _S_copy(__newp + __pos + __len2, __p + __len1, __how_much);
> diff --git a/libstdc++-v3/include/bits/memory_resource.h
> b/libstdc++-v3/include/bits/memory_resource.h
> index e5c6697b07e..ee3872e2bcd 100644
> --- a/libstdc++-v3/include/bits/memory_resource.h
> +++ b/libstdc++-v3/include/bits/memory_resource.h
> @@ -468,6 +468,22 @@ 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 `n` objects
> +       *          of type `value_type`.
> +       *
> +       *  Returns `a.allocate(n)`.
> +      */
> +      [[nodiscard]] static auto
> +      allocate_at_least(allocator_type& __a, size_type __n)
> +       -> std::allocation_result<pointer, size_type>
> +      { return { __a.allocate(__n), __n }; }
> +#endif
> +
>        /**
>         *  @brief  Deallocate memory.
>         *  @param  __a  An allocator.
> diff --git a/libstdc++-v3/include/bits/new_allocator.h
> b/libstdc++-v3/include/bits/new_allocator.h
> index fbe03e392aa..13018036e3a 100644
> --- a/libstdc++-v3/include/bits/new_allocator.h
> +++ b/libstdc++-v3/include/bits/new_allocator.h
> @@ -162,6 +162,49 @@ _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");
> +
> +       const std::size_t __align_mask = __STDCPP_DEFAULT_NEW_ALIGNMENT__
> - 1;
> +
> +       if constexpr (!requires { sizeof(_Tp); })
> +         return { nullptr, 0 }; // static_assert already failed
> +       else if (__builtin_expect(__n > this->_M_max_size(), false))
> +         {
> +           if (__n > (std::size_t(-1) / sizeof(_Tp)))
> +             std::__throw_bad_array_new_length();
> +           std::__throw_bad_alloc();
> +         }
> +       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 if constexpr (sizeof(_Tp) > __align_mask)
> +         {
> +           auto __p = _GLIBCXX_OPERATOR_NEW(__n * sizeof(_Tp));
> +           return { static_cast<_Tp*>(__p), __n };
> +         }
> +       else // See if more _Tp elements can fit into the allocation.
> +         {
> +           const auto __need = __n * sizeof(_Tp);
> +           const auto __ask = (__need + __align_mask) & ~__align_mask;
> +           const auto __p = _GLIBCXX_OPERATOR_NEW(__ask);
> +           using _Uchar = const unsigned char;
> +           static_assert(sizeof(_Tp) <= _Uchar(-1));
> +           // Use 8-bit division.
> +           _Uchar __spare = __ask - __need, __size = sizeof(_Tp);
> +           return { static_cast<_Tp*>(__p), __n + __spare / __size };
> +         }
> +      }
> +#endif
> +
>        // __p is not permitted to be a null pointer.
>        _GLIBCXX20_CONSTEXPR void
>        deallocate(_Tp* __p, size_type __n __attribute__ ((__unused__)))
> diff --git a/libstdc++-v3/include/bits/stl_vector.h
> b/libstdc++-v3/include/bits/stl_vector.h
> index c4ca214752a..571018cb6f7 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
> @@ -381,12 +394,31 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>      public:
>        _Vector_impl _M_impl;
>
> +      struct _Alloc_result { pointer __ptr; size_t __count; };
> +
>        _GLIBCXX20_CONSTEXPR
> -      pointer
> -      _M_allocate(size_t __n)
> +      _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
> +           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;
> +#else
> +           __r.__ptr = _Tr::allocate(_M_impl, __n), __r.__count = __n;
> +#endif
> +         }
> +       else __r.__ptr = pointer(), __r.__count = 0;
> +       return __r;
>        }
>
>        _GLIBCXX20_CONSTEXPR
> @@ -404,9 +436,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 +512,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;
> @@ -535,7 +568,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>  #endif // C++11
>
>      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 +1149,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 +1715,18 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>                   return;
>                 }
>
> -             const size_type __len = _M_check_len(__n,
> "vector::append_range");
> +             const size_type __len1 = _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, __count] = this->_M_allocate_at_least(__len1);
> +             const size_type __len = __count;
> +             const pointer __start = __ptr;
>               const pointer __mid = __start + __sz;
>               const pointer __back = __mid + __n;
>               _Guard_alloc __guard(__start, __len, *this);
> +             allocator_type& __a = _M_get_Tp_allocator();
>               std::__uninitialized_copy_a(ranges::begin(__rg),
>                                           ranges::end(__rg),
>                                           __mid, __a);
> @@ -1897,7 +1932,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>         _M_allocate_and_copy(size_type __n,
>                              _ForwardIterator __first, _ForwardIterator
> __last)
>         {
> -         _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();
> @@ -1916,8 +1952,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>         _M_initialize_dispatch(_Integer __int_n, _Integer __value,
> __true_type)
>         {
>           const size_type __n = static_cast<size_type>(__int_n);
> -         pointer __start =
> -           _M_allocate(_S_check_init_len(__n, _M_get_Tp_allocator()));
> +         pointer __start = _M_allocate_at_least(
> +             _S_check_init_len(__n, _M_get_Tp_allocator())).__ptr;
>           this->_M_impl._M_start = __start;
>           this->_M_impl._M_end_of_storage = __start + __n;
>

Doesn't this just discard any additional capacity that was allocated?

          _M_fill_initialize(__n, __value);
> @@ -1971,10 +2007,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 +2228,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..9524038b7d1 100644
> --- a/libstdc++-v3/include/bits/vector.tcc
> +++ b/libstdc++-v3/include/bits/vector.tcc
> @@ -79,7 +79,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>  #if __cplusplus >= 201103L
>           if constexpr (_S_use_relocate())
>             {
> -             __tmp = this->_M_allocate(__n);
> +             auto __res = this->_M_allocate_at_least(__n);
> +             __tmp = __res.__ptr;
> +             __n = __res.__count;
>               std::__relocate_a(this->_M_impl._M_start,
> this->_M_impl._M_finish,
>                                 __tmp, _M_get_Tp_allocator());
>             }
> @@ -106,12 +108,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 +127,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
> @@ -464,13 +466,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 +578,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 +658,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 +722,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
>                 {
> @@ -787,7 +795,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>
>            const size_type __len =
>              _M_check_len(__n, "vector::_M_fill_append");
> -          pointer __new_start(this->_M_allocate(__len));
> +          _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
>              {
> @@ -852,9 +862,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 +1015,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 __len1 =
>                   _M_check_len(__n, "vector::_M_range_insert");
>  #if __cplusplus < 201103L
> -               if (__len < (__n + (__old_finish - __old_start)))
> +               if (__len1 < (__n + (__old_finish - __old_start)))
>                   __builtin_unreachable();
>  #endif
>
> -               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
>                   {
> @@ -1111,7 +1125,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>               }
>             else // Reallocate
>               {
> -               const size_type __len
> +               const size_type __len1
>                   = _M_check_len(__n, "vector::insert_range");
>
>                 struct _Guard : _Guard_alloc
> @@ -1130,7 +1144,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>                 };
>
>                 // Allocate new storage:
> -               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 __guard(__new_start, __len, *this);
>
>                 auto& __alloc = _M_get_Tp_allocator();
> 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
>  #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
>  #define __glibcxx_want_allocator_traits_is_always_equal
>  #define __glibcxx_want_constexpr_vector
>  #define __glibcxx_want_containers_ranges
> diff --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
> index 44c8391ebc2..9942e1cfb6a 100644
> ---
> 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
> @@ -34,7 +34,7 @@ struct Alloc : std::allocator<T>
>    { return personality == a.personality; }
>  };
>
> -constexpr bool
> +consteexpr bool
>

Typo

 test_default_ctor()
>  {
>    std::basic_string<C> s0;
> diff --git a/libstdc++-v3/testsuite/util/testsuite_allocator.h
> b/libstdc++-v3/testsuite/util/testsuite_allocator.h
> index 892a385e307..d8914229c7a 100644
> --- a/libstdc++-v3/testsuite/util/testsuite_allocator.h
> +++ b/libstdc++-v3/testsuite/util/testsuite_allocator.h
> @@ -176,6 +176,16 @@ namespace __gnu_test
>         return p;
>        }
>
> +#ifdef __glibcxx_allocate_at_least
> +      std::allocation_result<pointer, size_type>
> +      allocate_at_least(size_type n)
> +      {
> +       auto r = AllocTraits::allocate_at_least(*this, n);
>

Is this recursive now?

+       counter_type::allocate(r.count * sizeof(T));
> +       return r;
> +      }
> +#endif
> +
>  #if __cplusplus >= 201103L
>        template<typename U, typename... Args>
>         void
> @@ -353,6 +363,9 @@ namespace __gnu_test
>        _GLIBCXX20_CONSTEXPR
>        pointer
>        allocate(size_type n, const void* = 0)
> +#ifdef __glibcxx_allocate_at_least
> +      { return this->allocate_at_least(n).ptr; }
> +#else
>        {
>         pointer p = AllocTraits::allocate(*this, n);
>
> @@ -372,6 +385,33 @@ namespace __gnu_test
>
>         return p;
>        }
> +#endif
> +
> +#ifdef __glibcxx_allocate_at_least
> +      _GLIBCXX20_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())
> +         return r;
> +
> +       try
> +         {
> +           get_map().insert(map_type::value_type(
> +                 reinterpret_cast<void*>(r.ptr), personality));
> +         }
> +       catch(...)
> +         {
> +           AllocTraits::deallocate(*this, r.ptr, r.count);
> +           __throw_exception_again;
> +         }
> +
> +       return r;
> +      }
> +#endif
>
>        _GLIBCXX14_CONSTEXPR
>        void
> --
> 2.53.0
>
>
  

Patch

diff --git a/libstdc++-v3/include/bits/alloc_traits.h b/libstdc++-v3/include/bits/alloc_traits.h
index 2be8ed561d4..b773be365bc 100644
--- a/libstdc++-v3/include/bits/alloc_traits.h
+++ b/libstdc++-v3/include/bits/alloc_traits.h
@@ -419,7 +419,7 @@  _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);
@@ -672,7 +672,12 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       [[nodiscard]] static constexpr auto
       allocate_at_least(allocator_type __a, size_type __n)
 	-> allocation_result<pointer, size_type>
-      { return __a.allocate_at_least(__n); }
+      {
+	if constexpr(requires { __a.__allocate_at_least(__n); })
+	  return __a.allocate_at_least(__n);
+	else
+	  return { __a.allocate(__n), __n };
+      }
 #endif
 
       /**
diff --git a/libstdc++-v3/include/bits/allocator.h b/libstdc++-v3/include/bits/allocator.h
index 9c22c805ebe..ec160f44a1e 100644
--- a/libstdc++-v3/include/bits/allocator.h
+++ b/libstdc++-v3/include/bits/allocator.h
@@ -219,12 +219,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 202a911f9ef..09a87d27910 100644
--- a/libstdc++-v3/include/bits/basic_string.h
+++ b/libstdc++-v3/include/bits/basic_string.h
@@ -135,10 +135,18 @@  _GLIBCXX_BEGIN_NAMESPACE_CXX11
 #endif
 
     private:
-      static _GLIBCXX20_CONSTEXPR pointer
-      _S_allocate(_Char_alloc_type& __a, size_type __n)
+      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.
@@ -146,9 +154,9 @@  _GLIBCXX_BEGIN_NAMESPACE_CXX11
 	  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]));
+	      std::construct_at(__builtin_addressof(__r.__ptr[__i]));
 #endif
-	return __p;
+	return __r;
       }
 
 #ifdef __glibcxx_string_view // >= C++17
@@ -1782,10 +1790,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);
 		    _M_set_length(__len);
 		  }
 	      }
diff --git a/libstdc++-v3/include/bits/basic_string.tcc b/libstdc++-v3/include/bits/basic_string.tcc
index a223edf67ac..c56b214a3fd 100644
--- a/libstdc++-v3/include/bits/basic_string.tcc
+++ b/libstdc++-v3/include/bits/basic_string.tcc
@@ -161,7 +161,10 @@  _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);
+      _Alloc_result __r = _S_allocate_at_least(
+	_M_get_allocator(), __capacity + 1);
+      __capacity = __r.__count - 1;
+      return __r.__ptr;
     }
 
   // NB: This is the special case for Input Iterators, used in
@@ -444,11 +447,12 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       else if (__length < __capacity)
 	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 +592,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;
 	      _S_copy(__newp, this->_M_data(), __pos);
 	      _S_copy(__newp + __pos, __s, __len2);
 	      _S_copy(__newp + __pos + __len2, __p + __len1, __how_much);
diff --git a/libstdc++-v3/include/bits/memory_resource.h b/libstdc++-v3/include/bits/memory_resource.h
index e5c6697b07e..ee3872e2bcd 100644
--- a/libstdc++-v3/include/bits/memory_resource.h
+++ b/libstdc++-v3/include/bits/memory_resource.h
@@ -468,6 +468,22 @@  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 `n` objects
+       *          of type `value_type`.
+       *
+       *  Returns `a.allocate(n)`.
+      */
+      [[nodiscard]] static auto
+      allocate_at_least(allocator_type& __a, size_type __n)
+	-> std::allocation_result<pointer, size_type>
+      { return { __a.allocate(__n), __n }; }
+#endif
+
       /**
        *  @brief  Deallocate memory.
        *  @param  __a  An allocator.
diff --git a/libstdc++-v3/include/bits/new_allocator.h b/libstdc++-v3/include/bits/new_allocator.h
index fbe03e392aa..13018036e3a 100644
--- a/libstdc++-v3/include/bits/new_allocator.h
+++ b/libstdc++-v3/include/bits/new_allocator.h
@@ -162,6 +162,49 @@  _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");
+
+	const std::size_t __align_mask = __STDCPP_DEFAULT_NEW_ALIGNMENT__ - 1;
+
+	if constexpr (!requires { sizeof(_Tp); })
+	  return { nullptr, 0 }; // static_assert already failed
+	else if (__builtin_expect(__n > this->_M_max_size(), false))
+	  {
+	    if (__n > (std::size_t(-1) / sizeof(_Tp)))
+	      std::__throw_bad_array_new_length();
+	    std::__throw_bad_alloc();
+	  }
+	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 if constexpr (sizeof(_Tp) > __align_mask)
+	  {
+	    auto __p = _GLIBCXX_OPERATOR_NEW(__n * sizeof(_Tp));
+	    return { static_cast<_Tp*>(__p), __n };
+	  }
+	else // See if more _Tp elements can fit into the allocation.
+	  {
+	    const auto __need = __n * sizeof(_Tp);
+	    const auto __ask = (__need + __align_mask) & ~__align_mask;
+	    const auto __p = _GLIBCXX_OPERATOR_NEW(__ask);
+	    using _Uchar = const unsigned char;
+	    static_assert(sizeof(_Tp) <= _Uchar(-1));
+	    // Use 8-bit division.
+	    _Uchar __spare = __ask - __need, __size = sizeof(_Tp);
+	    return { static_cast<_Tp*>(__p), __n + __spare / __size };
+	  }
+      }
+#endif
+
       // __p is not permitted to be a null pointer.
       _GLIBCXX20_CONSTEXPR void
       deallocate(_Tp* __p, size_type __n __attribute__ ((__unused__)))
diff --git a/libstdc++-v3/include/bits/stl_vector.h b/libstdc++-v3/include/bits/stl_vector.h
index c4ca214752a..571018cb6f7 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
@@ -381,12 +394,31 @@  _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
     public:
       _Vector_impl _M_impl;
 
+      struct _Alloc_result { pointer __ptr; size_t __count; };
+
       _GLIBCXX20_CONSTEXPR
-      pointer
-      _M_allocate(size_t __n)
+      _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
+	    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;
+#else
+	    __r.__ptr = _Tr::allocate(_M_impl, __n), __r.__count = __n;
+#endif
+	  }
+	else __r.__ptr = pointer(), __r.__count = 0;
+	return __r;
       }
 
       _GLIBCXX20_CONSTEXPR
@@ -404,9 +436,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 +512,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;
@@ -535,7 +568,7 @@  _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 #endif // C++11
 
     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 +1149,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 +1715,18 @@  _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 		  return;
 		}
 
-	      const size_type __len = _M_check_len(__n, "vector::append_range");
+	      const size_type __len1 = _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, __count] = this->_M_allocate_at_least(__len1);
+	      const size_type __len = __count;
+	      const pointer __start = __ptr;
 	      const pointer __mid = __start + __sz;
 	      const pointer __back = __mid + __n;
 	      _Guard_alloc __guard(__start, __len, *this);
+	      allocator_type& __a = _M_get_Tp_allocator();
 	      std::__uninitialized_copy_a(ranges::begin(__rg),
 					  ranges::end(__rg),
 					  __mid, __a);
@@ -1897,7 +1932,8 @@  _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 	_M_allocate_and_copy(size_type __n,
 			     _ForwardIterator __first, _ForwardIterator __last)
 	{
-	  _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();
@@ -1916,8 +1952,8 @@  _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 	_M_initialize_dispatch(_Integer __int_n, _Integer __value, __true_type)
 	{
 	  const size_type __n = static_cast<size_type>(__int_n);
-	  pointer __start =
-	    _M_allocate(_S_check_init_len(__n, _M_get_Tp_allocator()));
+	  pointer __start = _M_allocate_at_least(
+	      _S_check_init_len(__n, _M_get_Tp_allocator())).__ptr;
 	  this->_M_impl._M_start = __start;
 	  this->_M_impl._M_end_of_storage = __start + __n;
 	  _M_fill_initialize(__n, __value);
@@ -1971,10 +2007,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 +2228,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..9524038b7d1 100644
--- a/libstdc++-v3/include/bits/vector.tcc
+++ b/libstdc++-v3/include/bits/vector.tcc
@@ -79,7 +79,9 @@  _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 #if __cplusplus >= 201103L
 	  if constexpr (_S_use_relocate())
 	    {
-	      __tmp = this->_M_allocate(__n);
+	      auto __res = this->_M_allocate_at_least(__n);
+	      __tmp = __res.__ptr;
+	      __n = __res.__count;
 	      std::__relocate_a(this->_M_impl._M_start, this->_M_impl._M_finish,
 				__tmp, _M_get_Tp_allocator());
 	    }
@@ -106,12 +108,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 +127,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
@@ -464,13 +466,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 +578,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 +658,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 +722,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
 		{
@@ -787,7 +795,9 @@  _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 
 	   const size_type __len =
 	     _M_check_len(__n, "vector::_M_fill_append");
-	   pointer __new_start(this->_M_allocate(__len));
+	   _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
 	     {
@@ -852,9 +862,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 +1015,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 __len1 =
 		  _M_check_len(__n, "vector::_M_range_insert");
 #if __cplusplus < 201103L
-		if (__len < (__n + (__old_finish - __old_start)))
+		if (__len1 < (__n + (__old_finish - __old_start)))
 		  __builtin_unreachable();
 #endif
 
-		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
 		  {
@@ -1111,7 +1125,7 @@  _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 	      }
 	    else // Reallocate
 	      {
-		const size_type __len
+		const size_type __len1
 		  = _M_check_len(__n, "vector::insert_range");
 
 		struct _Guard : _Guard_alloc
@@ -1130,7 +1144,9 @@  _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 		};
 
 		// Allocate new storage:
-		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 __guard(__new_start, __len, *this);
 
 		auto& __alloc = _M_get_Tp_allocator();
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
 #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
 #define __glibcxx_want_allocator_traits_is_always_equal
 #define __glibcxx_want_constexpr_vector
 #define __glibcxx_want_containers_ranges
diff --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
index 44c8391ebc2..9942e1cfb6a 100644
--- 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
@@ -34,7 +34,7 @@  struct Alloc : std::allocator<T>
   { return personality == a.personality; }
 };
 
-constexpr bool
+consteexpr bool
 test_default_ctor()
 {
   std::basic_string<C> s0;
diff --git a/libstdc++-v3/testsuite/util/testsuite_allocator.h b/libstdc++-v3/testsuite/util/testsuite_allocator.h
index 892a385e307..d8914229c7a 100644
--- a/libstdc++-v3/testsuite/util/testsuite_allocator.h
+++ b/libstdc++-v3/testsuite/util/testsuite_allocator.h
@@ -176,6 +176,16 @@  namespace __gnu_test
 	return p;
       }
 
+#ifdef __glibcxx_allocate_at_least
+      std::allocation_result<pointer, size_type>
+      allocate_at_least(size_type n)
+      {
+	auto r = AllocTraits::allocate_at_least(*this, n);
+	counter_type::allocate(r.count * sizeof(T));
+	return r;
+      }
+#endif
+
 #if __cplusplus >= 201103L
       template<typename U, typename... Args>
 	void
@@ -353,6 +363,9 @@  namespace __gnu_test
       _GLIBCXX20_CONSTEXPR
       pointer
       allocate(size_type n, const void* = 0)
+#ifdef __glibcxx_allocate_at_least
+      { return this->allocate_at_least(n).ptr; }
+#else
       {
 	pointer p = AllocTraits::allocate(*this, n);
 
@@ -372,6 +385,33 @@  namespace __gnu_test
 
 	return p;
       }
+#endif
+
+#ifdef __glibcxx_allocate_at_least
+      _GLIBCXX20_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())
+	  return r;
+
+	try
+	  {
+	    get_map().insert(map_type::value_type(
+		  reinterpret_cast<void*>(r.ptr), personality));
+	  }
+	catch(...)
+	  {
+	    AllocTraits::deallocate(*this, r.ptr, r.count);
+	    __throw_exception_again;
+	  }
+
+	return r;
+      }
+#endif
 
       _GLIBCXX14_CONSTEXPR
       void