On Sun, 8 Mar 2026, 23:39 Nathan Myers, <[email protected]> wrote:

> Have you thought about how std::allocator<>::allocate_at_least
> might usefully discover whether the ::op new (it is obliged by the
> Standard to use) is the one in libstdc++, and will supply extra
> details about memory allocated, not one the user has supplied and
> the linker has substituted in its place? Ideally it would all be
> decided at link time, and not conditionally in every call, but
> linker magic is fragile even without LTO. And users can call ::op
> new directly, with no #include to declare it.
>
> Maybe pass an extra argument to ::op new that the user's version
> won't notice, and that ours may discover is present by looking at
> the stack frame? The extra argument might be a pointer to a place
> to scribble extra details. (A hint argument might be placed there,
> besides.) But stack frame conventions vary too.
>

We don't need to do that. This feature is a customisation point for
non-standard allocators (and program-defined specializations of
std::allocator<User type>), we don't need to make std::allocator use
it. P0901 proposed changes to operator new which would have been useful
here, but that got abandoned.

I did post a prototype in https://gcc.gnu.org/PR106477 which can detect
whether operator new has been replaced, but I don't think we would need
that here.

We could just have a thread_local size_t* which is initially null and which
std::allocator would set to a local size_t, and operator new could check
for non-null and conditionally write the allocated size to it. But that
assumes that malloc provides a way to get the size. If a user interposes
their own malloc but doesn't work replace malloc_usable_size then you'd
have UB.

A safer and more conservative approach would be for
std::allocator::allocate_at_least to just round up to a multiple of
alignof(max_align_t) (as long as the total number of bytes being allocated
is already at least alignof(max_align_t)). That should be a separate change
though.


> -N
>
> On 3/8/26 7:01 AM, Jonathan Wakely wrote:
> > On Sat, 7 Mar 2026, 12:38 Nathan Myers, <[email protected]
> >     +       *  @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.
> Good.
>
> >     +       *
> >     +       *  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?
>
> Just that it writes out the error message attached.
>

Ah yes, the requires-expression means we get a failed static_assert:

/home/jwakely/gcc/16/include/c++/16.0.1/bits/new_allocator.h:131:23:
error: static
assertion failed: cannot allocate incomplete types
 131 |         static_assert(requires { sizeof(_Tp); }, "cannot allocate
incomplete types");
     |                       ^~~~~~~~~~~~~~~~~~~~~~~~~
 * 'false' evaluates to false

instead of an invalid one that doesn't even get as far as testing the
boolean expression:

/home/jwakely/gcc/16/include/c++/16.0.1/bits/new_allocator.h:131:23:
error: invalid
application of 'sizeof' to incomplete type 'S'
 131 |         static_assert(sizeof(_Tp) != 0, "cannot allocate incomplete
types");
     |                       ^~~~~~~~~~~

But then it would be better to improve the existing checks in
allocator::allocate instead of adding another one here. That can be done
separately, so for this patch please don't add a new static_assert, we
already get a diagnostic from the allocate(n) call.


>
> >     +       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.
>
> But, does it?


Yes

Maybe it should. I have it here only because the Standard
> says "Mandates:", which I have (mistakenly?) interpreted as requiring
> a call-site diagnostic.
>

The standard only requires it too be ill-formed, which requires a
diagnostic, nothing about where it happens.


> >     --- 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
>
> Good catch.
>
> >     +    {
> >     +      _Pointer ptr;
> >     +      _Size count;
> >     +    };
> >     +#endif
> >     +
> >
> ... >     +#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
>
> See, no static_assert here.
>

It's inside the call to allocate(__n).

prog.cc:5:39: required from here
.../include/c++/13.2.0/bits/allocator.h:193:45: error: invalid application
of 'sizeof' to incomplete type 'S'

Reply via email to