On Sat, 7 Mar 2026, 12:38 Nathan Myers, <[email protected]> wrote:

> [Q: Is there anything we would need to do now, to enable later
> changing std::allocator<>::allocate_at_least() behavior without
> breaking ABI?]
>
> Implement proposals adopted for C++23:
> P0401R6, "Providing size feedback in the Allocator interface"
> P2652R2, "Disallow User Specialization of allocator_traits".
>
> libstdc++-v3/ChangeLog:
>         PR libstdc++/118030
>         * include/bits/alloc_traits.h (allocate_at_least (2x)): Define.
>         * include/bits/allocator.h (allocation_result, allocate_at_least):
>         Define.
>         * include/std/memory (__glibcxx_want_allocate_at_least): 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      | 47 ++++++++++++++
>  libstdc++-v3/include/bits/allocator.h         | 15 +++++
>  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 +++++++
>  7 files changed, 170 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..12a7423375a 100644
> --- a/libstdc++-v3/include/bits/alloc_traits.h
> +++ b/libstdc++-v3/include/bits/alloc_traits.h
> @@ -404,6 +404,33 @@ _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 @a n or more
> +       *  contiguous objects of type @c value_type .
>

Please use markdown for new doxygen comments instead of doxygen's @c  or
html like <tt>. In general only back ticks are needed, and they're more
readable then the alternatives.



+       *
> +       *  Returns <tt> a.allocate_at_least(n) </tt> if that expression
> +       *  is well-formed, else <tt> { a.allocate(n), n } </tt>. When an
> +       *  allocator is obliged to reserve more space than required for
> +       *  the cited @c 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>
> +      {
> +       static_assert(requires { sizeof(value_type); },
> +         "allocated object type must be complete");
>

Does using a requires-expression make a difference here, rather than just
sizeof directly?



+       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 +662,26 @@ _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 @a n or more
> +       *  contiguous objects of type @c value_type .
> +       *
> +       *  Returns <tt> a.allocate_at_least(n) </tt>.
> +      */
> +      [[nodiscard]] static constexpr auto
> +      allocate_at_least(allocator_type __a, size_type __n)
> +       -> allocation_result<pointer, size_type>
> +      {
> +       static_assert(requires { sizeof(value_type); },
> +         "allocated object type must be complete");
>

I think this static_assert is redundant, because
std::allocator::allocate_at_least calls std::allocator::allocate which
checks the same condition.

+       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..2c6f1103cb5 100644
> --- a/libstdc++-v3/include/bits/allocator.h
> +++ b/libstdc++-v3/include/bits/allocator.h
> @@ -61,6 +61,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>     *  @{
>     */
>
> +#ifdef __glibcxx_allocate_at_least  // C++23
> +  template <typename _Pointer, typename _Size = size_t>
> +    struct allocation_result
>

Defining this type here means it won't be declared in bits/alloc_traits.h
for freestanding, because of:

# if _GLIBCXX_HOSTED
#  include <bits/allocator.h>
# endif

I think it would be better in bits/memoryfwd.h



> +    {
> +      _Pointer ptr;
> +      _Size count;
> +    };
> +#endif
> +
>    // Since C++20 the primary template should be used for allocator<void>,
>    // but then it would have a non-trivial default ctor and dtor for C++20,
>    // but trivial for C++98-17, which would be an ABI incompatibility
> between
> @@ -216,6 +225,12 @@ _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/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