[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