[PATCHv3] libstdc++: Use allocate_at_least in vector, string (P0401) [PR118030]
Jonathan Wakely
jwakely@redhat.com
Wed May 13 10:52:29 GMT 2026
On Tue, 12 May 2026 at 20:12 +0100, Jonathan Wakely wrote:
>It occurs to me now that we could remove all the code here that's
>duplicated from allocate(size_type, void_pointer) with something like
>this:
>
> [[nodiscard]]
> constexpr std::allocation_result<_Tp*, size_t>
> allocate_at_least(size_t __n)
> {
> if ! consteval {
> if constexpr (requires { sizeof(_Tp); })
> if constexpr (alignof(_Tp) <= __STDCPP_DEFAULT_NEW_ALIGNMENT__
> && sizeof(_Tp) < __STDCPP_DEFAULT_NEW_ALIGNMENT__)
> if (__builtin_expect(__n <= this->_M_max_size(), true))
> {
> const size_t __need = __n * sizeof(_Tp);
> const size_t __mask = __STDCPP_DEFAULT_NEW_ALIGNMENT__ - 1;
> size_t __ask = (__need + __mask) & ~__mask;
> // Avoid rounding to and asking for 2^63 bytes (PR108377):
> __ask -= __ask >> (__SIZE_WIDTH__ - 1);
> auto* __p = static_cast<_Tp*>(_GLIBCXX_OPERATOR_NEW(__ask));
> using _U8 = const unsigned char;
> static_assert(sizeof(_Tp) <= ~_U8());
> // Use 8-bit division for minimal latency:
> _U8 __spare = __ask - __need, __size = sizeof(_Tp);
> return { __p, __n + __spare / __size };
> }
> }
> return { allocate(__n), __n };
> }
>
>i.e. only do the new part here where we actually want to round up, and
>otherwise just call allocate(n).
>
>So the static_assert, exceptional path, consteval path, and the
>not-rounded-up case all use the existing code.
The sketch above is ugly, but so is duplicating all the logic from the
original allocate function. That logic gets quite a lot of churn,
here's the git blame output:
fe9571a35db53 (Jonathan Wakely 2021-12-01) #if __cplusplus >= 201103L
29da01709facb (Jonathan Wakely 2022-06-14) // _GLIBCXX_RESOLVE_LIB_DEFECTS
29da01709facb (Jonathan Wakely 2022-06-14) // 3308. std::allocator<void>().allocate(n)
f60dbb5796798 (Jonathan Wakely 2026-03-09) #if ! __cpp_concepts
29da01709facb (Jonathan Wakely 2022-06-14) static_assert(sizeof(_Tp) != 0, "cannot allocate incomplete types");
f60dbb5796798 (Jonathan Wakely 2026-03-09) #else
f60dbb5796798 (Jonathan Wakely 2026-03-09) static_assert(requires { sizeof(_Tp); },
f60dbb5796798 (Jonathan Wakely 2026-03-09) "cannot allocate incomplete types");
fe9571a35db53 (Jonathan Wakely 2021-12-01)
f60dbb5796798 (Jonathan Wakely 2026-03-09) if constexpr (!requires { sizeof(_Tp); })
f60dbb5796798 (Jonathan Wakely 2026-03-09) return nullptr; // static_assert already failed
f60dbb5796798 (Jonathan Wakely 2026-03-09) else
f60dbb5796798 (Jonathan Wakely 2026-03-09) #endif
f60dbb5796798 (Jonathan Wakely 2026-03-09) #endif
fe9571a35db53 (Jonathan Wakely 2021-12-01) if (__builtin_expect(__n > this->_M_max_size(), false))
fe9571a35db53 (Jonathan Wakely 2021-12-01) {
fe9571a35db53 (Jonathan Wakely 2021-12-01) // _GLIBCXX_RESOLVE_LIB_DEFECTS
fe9571a35db53 (Jonathan Wakely 2021-12-01) // 3190. allocator::allocate sometimes returns too little storage
fe9571a35db53 (Jonathan Wakely 2021-12-01) if (__n > (std::size_t(-1) / sizeof(_Tp)))
fe9571a35db53 (Jonathan Wakely 2021-12-01) std::__throw_bad_array_new_length();
fe9571a35db53 (Jonathan Wakely 2021-12-01) std::__throw_bad_alloc();
fe9571a35db53 (Jonathan Wakely 2021-12-01) }
03d3aeb0e0fa7 (Jonathan Wakely 2024-06-26) #if __cpp_aligned_new && __cplusplus >= 201103L
f60dbb5796798 (Jonathan Wakely 2026-03-09) else if constexpr (alignof(_Tp) > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
fe9571a35db53 (Jonathan Wakely 2021-12-01) {
fe9571a35db53 (Jonathan Wakely 2021-12-01) std::align_val_t __al = std::align_val_t(alignof(_Tp));
fe9571a35db53 (Jonathan Wakely 2021-12-01) return static_cast<_Tp*>(_GLIBCXX_OPERATOR_NEW(__n * sizeof(_Tp),
fe9571a35db53 (Jonathan Wakely 2021-12-01) __al));
fe9571a35db53 (Jonathan Wakely 2021-12-01) }
fe9571a35db53 (Jonathan Wakely 2021-12-01) #endif
f60dbb5796798 (Jonathan Wakely 2026-03-09) else
f60dbb5796798 (Jonathan Wakely 2026-03-09) return static_cast<_Tp*>(_GLIBCXX_OPERATOR_NEW(__n * sizeof(_Tp)));
So it changes around once a year, to add better checks, or replace
operator new with _GLIBCXX_OPERATOR_NEW, or other refactoring.
If we duplicate it in allocate_at_least then we have to change both
places every time.
More information about the Libstdc++
mailing list