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

Nathan Myers ncm@cantrip.org
Tue Apr 21 17:35:42 GMT 2026


Thank you.

On 4/20/26 10:16 AM, Jonathan Wakely wrote:
> 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 wait 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.

ACK.


>> #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.

This appeared to be necessary to get tests involving apparatus in
testsuite/util/testsuite_allocator.* to build.


>> +        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

ACK.


>> +      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.

ACK.


>> +      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.

I made a separate function because it seemed like it would be useful
for users upgrading their own allocators.

But I will fold it all back into __new_allocator::allocate_at_least,
and let users copy and paste from that.


>> +    -> 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)));
> 
> ?

On x86, the rotl resolves to the same code, but does not depend
on how big _Size is. Folded into __new_allocator, there is no
uncertainty about that, so I have written

   __ask -= __ask >> (__SIZE_WIDTH__ - 1);

which is shorter and resolves to one instruction less. Compilers
do not like generating a bit-test instruction for this, even though
that helpfully sets the carry bit; probably something about leaving
the instruction scheduler more freedom.


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

Div instruction cycle count strongly depends on operand size,
particularly on smaller targets.


>> +      _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.

ACK.
It is unfortunate how its demand for a block after its `else`
affects formatting.


>> +      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).

ACK.


>> +      }
>> +#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.

If restored, it would anyway only be used in a consteval context
in _M_replace.


>> +
>> +      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.

Yes, _M_create performs much the same work as _M_create_plus, just
with a really awful usage interface.


>> +      __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.

This is a consteval context, where _S_allocate_at_least just calls
allocate(n) anyway. This would be the only use of _S_allocate,
otherwise only an ABI-stability stub.


>>           _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.

ACK.
I guess when pmr::polymorphic_allocator gets allocate_at_least,
we can revisit.


>> +      {
>> +        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'

ACK.


>> +      {
>> +        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.

ACK.


>> +
>> +      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.

ACK.


>> +          }
>> +        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.

ACK.


>>     {
>> -      _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.

ACK.


>> +      {
>> +      _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).

ACK.


>> +      size_type __n, _ForwardIterator __first, _ForwardIterator __last)
> 
> Incorrect formatting again.

ACK.


>> +    {
>> +      _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_pointer type.
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.

ACK. I guess the specs for <string> and <vector> don't mention
allocate_at_least, making it an implemention detail?


>> #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.

ACK.


>> #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

ACK.


>> +      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.

ACK.

Probably p is some encapsulated thing.

>> +      }
>> +    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