[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 .
+ *
+ * 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");
+ 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");
+ 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
+ {
+ _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