On Mon, 9 Mar 2026 at 23:01, Jonathan Wakely <[email protected]> wrote:
>
>
>
> On Mon, 9 Mar 2026, 21:34 Nathan Myers, <[email protected]> wrote:
>>
>> Changes in v2:
>>  - Move 'struct allocation_result' so non-hosted users see it.
>>  - Use "`" markdown markup in new doxygen comments.
>>  - Remove redundant static_asserts re: incomplete types.
>>
>> Implement proposals adopted for C++23:
>> P0401R6, "Providing size feedback in the Allocator interface"
>> P2652R2, "Disallow User Specialization of allocator_traits".
>>
>> This is the minimal conforming implementation, i.e. without the
>> useful parts. Useful parts, to come in future patches, would
>> include giving access to any extra storage reserved, and use of
>> it in vector, string, and other contiguous containers.
>>
>> libstdc++-v3/ChangeLog:
>>         PR libstdc++/118030
>>         * include/bits/alloc_traits.h (allocate_at_least (2x)): Define.
>>         * include/bits/allocator.h (allocate_at_least): Define.
>>         * include/std/memory (__glibcxx_want_allocate_at_least): Define.
>>         * include/bits/memoryfwd.h (allocation_result): Define
>>         * include/bits/version.def (allocate_at_least): Add.
>>         * include/bits/version.h: Regenerate.
>>         * testsuite/20_util/allocator/allocate_at_least.cc: New test.
>>         * testsuite/20_util/allocator/allocate_at_least_neg.cc: New test.
>> ---
>>  libstdc++-v3/include/bits/alloc_traits.h      | 40 ++++++++++++
>>  libstdc++-v3/include/bits/allocator.h         |  9 +++
>>  libstdc++-v3/include/bits/memoryfwd.h         | 12 ++++
>>  libstdc++-v3/include/bits/version.def         |  8 +++
>>  libstdc++-v3/include/bits/version.h           | 10 +++
>>  libstdc++-v3/include/std/memory               |  1 +
>>  .../20_util/allocator/allocate_at_least.cc    | 65 +++++++++++++++++++
>>  .../allocator/allocate_at_least_neg.cc        | 24 +++++++
>>  8 files changed, 169 insertions(+)
>>  create mode 100644 
>> libstdc++-v3/testsuite/20_util/allocator/allocate_at_least.cc
>>  create mode 100644 
>> libstdc++-v3/testsuite/20_util/allocator/allocate_at_least_neg.cc
>>
>> diff --git a/libstdc++-v3/include/bits/alloc_traits.h 
>> b/libstdc++-v3/include/bits/alloc_traits.h
>> index c34143a3526..2be8ed561d4 100644
>> --- a/libstdc++-v3/include/bits/alloc_traits.h
>> +++ b/libstdc++-v3/include/bits/alloc_traits.h
>> @@ -404,6 +404,30 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>           return __a.allocate(__n);
>>        }
>>
>> +#ifdef __glibcxx_allocate_at_least  // C++23
>> +      /**
>> +       *  @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`.
>> +       *
>> +       *  Returns `a.allocate_at_least(n)` if that expression is
>> +       *  well-formed, else `{ a.allocate(n), n }`. When an allocator
>> +       *  is obliged to reserve more space than required for the cited
>> +       *  `n` objects, it may deliver the extra space to the caller.
>> +      */
>> +      [[nodiscard]] static constexpr auto
>> +      allocate_at_least(_Alloc& __a, size_type __n)
>> +       -> allocation_result<pointer, size_type>
>> +      {
>> +       if constexpr (requires { __a.allocate_at_least(__n); })
>> +         return __a.allocate_at_least(__n);
>> +       else
>> +         return { __a.allocate(__n), __n };
>> +      }
>> +#endif
>> +
>>        /**
>>         *  @brief  Deallocate memory.
>>         *  @param  __a  An allocator.
>> @@ -635,6 +659,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>  #endif
>>        }
>>
>> +#ifdef __glibcxx_allocate_at_least  // C++23
>> +      /**
>> +       *  @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`.
>> +       *
>> +       *  Returns `a.allocate_at_least(n)`.
>> +      */
>> +      [[nodiscard]] static constexpr auto
>> +      allocate_at_least(allocator_type __a, size_type __n)
>> +       -> allocation_result<pointer, size_type>
>> +      { return __a.allocate_at_least(__n); }
>> +#endif
>> +
>>        /**
>>         *  @brief  Deallocate memory.
>>         *  @param  __a  An allocator.
>> diff --git a/libstdc++-v3/include/bits/allocator.h 
>> b/libstdc++-v3/include/bits/allocator.h
>> index 9f9526bd5b0..aab6b48fe42 100644
>> --- a/libstdc++-v3/include/bits/allocator.h
>> +++ b/libstdc++-v3/include/bits/allocator.h
>> @@ -216,6 +216,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>        }
>>  #endif // C++20
>>
>> +#ifdef __glibcxx_allocate_at_least  // C++23
>> +      // TODO: This should be altered to interact usefully with features
>> +      // of the libstdc++-provided ::operator new (as distinct from a
>> +      // hypothetical user-provided one the linker may substitute).
>
>
> Please remove this comment - making that change wasn't an intended part of 
> the feature.

Or maybe put it in bugzilla as an enhancement request, rather than as
a TODO in the code.

>
> OK for trunk with that change.
>
>
>
>> +      [[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/memoryfwd.h 
>> b/libstdc++-v3/include/bits/memoryfwd.h
>> index ca532ca39fa..9e6507978f5 100644
>> --- a/libstdc++-v3/include/bits/memoryfwd.h
>> +++ b/libstdc++-v3/include/bits/memoryfwd.h
>> @@ -78,6 +78,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>      struct allocator_traits;
>>  #endif
>>
>> +#ifdef __glibcxx_allocate_at_least  // C++23
>> +  // Result of, specifically, allocate_at_least(). `count` is the number
>> +  // of objects that may be indexed from `ptr`, not bytes.
>> +  template <typename _Pointer, typename _Size = size_t>
>> +    struct allocation_result
>> +    {
>> +      _Pointer ptr;
>> +      _Size count;
>> +    };
>> +#endif
>> +
>> +
>>    /// @} group memory
>>
>>  _GLIBCXX_END_NAMESPACE_VERSION
>> diff --git a/libstdc++-v3/include/bits/version.def 
>> b/libstdc++-v3/include/bits/version.def
>> index dbe95b8b79f..dd77524b5a9 100644
>> --- a/libstdc++-v3/include/bits/version.def
>> +++ b/libstdc++-v3/include/bits/version.def
>> @@ -88,6 +88,14 @@ ftms = {
>>    };
>>  };
>>
>> +ftms = {
>> +  name = allocate_at_least;
>> +  values = {
>> +    v = 202302;
>> +    cxxmin = 23;
>> +  };
>> +};
>> +
>>  ftms = {
>>    name = is_null_pointer;
>>    values = {
>> diff --git a/libstdc++-v3/include/bits/version.h 
>> b/libstdc++-v3/include/bits/version.h
>> index eee99847490..bef61baeba8 100644
>> --- a/libstdc++-v3/include/bits/version.h
>> +++ b/libstdc++-v3/include/bits/version.h
>> @@ -80,6 +80,16 @@
>>  #endif /* !defined(__cpp_lib_allocator_traits_is_always_equal) */
>>  #undef __glibcxx_want_allocator_traits_is_always_equal
>>
>> +#if !defined(__cpp_lib_allocate_at_least)
>> +# if (__cplusplus >= 202100L)
>> +#  define __glibcxx_allocate_at_least 202302L
>> +#  if defined(__glibcxx_want_all) || 
>> defined(__glibcxx_want_allocate_at_least)
>> +#   define __cpp_lib_allocate_at_least 202302L
>> +#  endif
>> +# endif
>> +#endif /* !defined(__cpp_lib_allocate_at_least) */
>> +#undef __glibcxx_want_allocate_at_least
>> +
>>  #if !defined(__cpp_lib_is_null_pointer)
>>  # if (__cplusplus >= 201103L)
>>  #  define __glibcxx_is_null_pointer 201309L
>> diff --git a/libstdc++-v3/include/std/memory 
>> b/libstdc++-v3/include/std/memory
>> index c9c9224e599..cbe9f5ad200 100644
>> --- a/libstdc++-v3/include/std/memory
>> +++ b/libstdc++-v3/include/std/memory
>> @@ -125,6 +125,7 @@
>>  #define __glibcxx_want_to_address
>>  #define __glibcxx_want_transparent_operators
>>  #define __glibcxx_want_smart_ptr_owner_equality
>> +#define __glibcxx_want_allocate_at_least
>>  #include <bits/version.h>
>>
>>  #if __cplusplus >= 201103L && __cplusplus <= 202002L && _GLIBCXX_HOSTED
>> diff --git a/libstdc++-v3/testsuite/20_util/allocator/allocate_at_least.cc 
>> b/libstdc++-v3/testsuite/20_util/allocator/allocate_at_least.cc
>> new file mode 100644
>> index 00000000000..5399096d294
>> --- /dev/null
>> +++ b/libstdc++-v3/testsuite/20_util/allocator/allocate_at_least.cc
>> @@ -0,0 +1,65 @@
>> +// { dg-do run { target c++23 } }
>> +
>> +#include <memory>
>> +#include <testsuite_hooks.h>
>> +
>> +struct X { int i = 0; };
>> +
>> +template <typename T>
>> +  struct A : std::allocator<T>
>> +  {
>> +    using Base = std::allocator<T>;
>> +
>> +    std::allocation_result<T*, size_t>
>> +    allocate_at_least(std::size_t n)
>> +      { return { this->Base::allocate(2*n), 2*n }; }
>> +  };
>> +
>> +template <typename T>
>> +  struct M : std::allocator<T>
>> +  {
>> +    using Base = std::allocator<T>;
>> +    T* allocate_at_least(size_t n) = delete;
>> +
>> +    T* keep;
>> +    T* allocate(std::size_t n)
>> +      {
>> +       keep = this->Base::allocate(n);
>> +       return keep;
>> +      }
>> +  };
>> +
>> +int main()
>> +{
>> +  std::allocator<X> native;
>> +  auto a1 = native.allocate_at_least(100);
>> +  static_assert(std::is_same_v<decltype(a1), std::allocation_result<X*>>);
>> +  VERIFY(a1.count == 100);
>> +  native.deallocate(a1.ptr, a1.count);
>> +
>> +  using std_traits = std::allocator_traits<std::allocator<X>>;
>> +  auto a2 = std_traits::allocate_at_least(native, 100);
>> +  static_assert(std::is_same_v<decltype(a2), std::allocation_result<X*>>);
>> +  VERIFY(a2.count == 100);
>> +  std_traits::deallocate(native, a2.ptr, a2.count);
>> +
>> +  A<X> custom;
>> +  auto a3 = custom.allocate_at_least(100);
>> +  static_assert(std::is_same_v<decltype(a3), std::allocation_result<X*>>);
>> +  VERIFY(a3.count == 200);
>> +  custom.deallocate(a3.ptr, a3.count);
>> +
>> +  using custom_traits = std::allocator_traits<A<X>>;
>> +  auto a4 = custom_traits::allocate_at_least(custom, 100);
>> +  static_assert(std::is_same_v<decltype(a4), std::allocation_result<X*>>);
>> +  VERIFY(a4.count == 200);
>> +  custom_traits::deallocate(custom, a4.ptr, a4.count);
>> +
>> +  M<X> minimal;
>> +  using minimal_traits = std::allocator_traits<M<X>>;
>> +  auto a5 = minimal_traits::allocate_at_least(minimal, 100);
>> +  static_assert(std::is_same_v<decltype(a5), std::allocation_result<X*>>);
>> +  VERIFY(a5.count == 100);
>> +  VERIFY(a5.ptr == minimal.keep);
>> +  minimal_traits::deallocate(minimal, a5.ptr, a5.count);
>> +}
>> diff --git 
>> a/libstdc++-v3/testsuite/20_util/allocator/allocate_at_least_neg.cc 
>> b/libstdc++-v3/testsuite/20_util/allocator/allocate_at_least_neg.cc
>> new file mode 100644
>> index 00000000000..7b50851b0f9
>> --- /dev/null
>> +++ b/libstdc++-v3/testsuite/20_util/allocator/allocate_at_least_neg.cc
>> @@ -0,0 +1,24 @@
>> +// { dg-do compile { target c++23 } }
>> +
>> +#include <memory>
>> +#include <testsuite_hooks.h>
>> +
>> +template <typename T>
>> +  struct A : std::allocator<T>
>> +  {
>> +    using Base = std::allocator<T>;
>> +    std::allocation_result<T*> allocate_at_least(size_t) = delete;
>> +
>> +    T* allocate(std::size_t n)
>> +      { return { this->Base::allocate(n), n }; }
>> +  };
>> +
>> +struct incomplete;
>> +
>> +int main()
>> +{
>> +  A<incomplete> a;
>> +  using traits = std::allocator_traits<A<incomplete>>;
>> +  (void) traits::allocate_at_least(a, 1); // { dg-error "from here" }
>> +  // { dg-error "object type must be complete" "" { target { *-*-* } } 0 }
>> +}
>> --
>> 2.52.0
>>

Reply via email to