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