diff mbox series

[committed] libstdc++: Fix missing members in std::allocator<void>

Message ID YJqx30w0h9JgLo8Z@redhat.com
State New
Headers show
Series [committed] libstdc++: Fix missing members in std::allocator<void> | expand

Commit Message

Jonathan Wakely May 11, 2021, 4:33 p.m. UTC
The changes in 75c6a925dab5b7af9ab47c10906cb0e140261cc2 were slightly
incorrect, because the converting constructor should be noexcept, and
the POCMA and is_always_equal traits should still be present in C++20.
This fixes it, and slightly refactors the preprocessor conditions and
order of members. Also add comments explaining things.

The non-standard construct and destroy members added for PR 78052 can be
private if allocator_traits<allocator<void>> is made a friend.

libstdc++-v3/ChangeLog:

	* include/bits/allocator.h (allocator<void>) [C++20]: Add
	missing noexcept to constructor. Restore missing POCMA and
	is_always_equal_traits.
	[C++17]: Make construct and destroy members private and
	declare allocator_traits as a friend.
	* include/bits/memoryfwd.h (allocator_traits): Declare.
	* include/ext/malloc_allocator.h (malloc_allocator::allocate):
	Add nodiscard attribute. Add static assertion for LWG 3307.
	* include/ext/new_allocator.h (new_allocator::allocate): Add
	static assertion for LWG 3307.
	* testsuite/20_util/allocator/void.cc: Check that converting
	constructor is noexcept. Check for propagation traits and
	size_type and difference_type. Check that pointer and
	const_pointer are gone in C++20.

Tested powerpc64le-linux. Committed to trunk. This needs to be
backported to 10 and 11, but I won't make the construct/destroy
members private on the branches.
commit 5e3a1ea3d89d62972e1f036b2ede37a80b880bdf
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Tue May 11 15:01:01 2021

    libstdc++: Fix missing members in std::allocator<void>
    
    The changes in 75c6a925dab5b7af9ab47c10906cb0e140261cc2 were slightly
    incorrect, because the converting constructor should be noexcept, and
    the POCMA and is_always_equal traits should still be present in C++20.
    This fixes it, and slightly refactors the preprocessor conditions and
    order of members. Also add comments explaining things.
    
    The non-standard construct and destroy members added for PR 78052 can be
    private if allocator_traits<allocator<void>> is made a friend.
    
    libstdc++-v3/ChangeLog:
    
            * include/bits/allocator.h (allocator<void>) [C++20]: Add
            missing noexcept to constructor. Restore missing POCMA and
            is_always_equal_traits.
            [C++17]: Make construct and destroy members private and
            declare allocator_traits as a friend.
            * include/bits/memoryfwd.h (allocator_traits): Declare.
            * include/ext/malloc_allocator.h (malloc_allocator::allocate):
            Add nodiscard attribute. Add static assertion for LWG 3307.
            * include/ext/new_allocator.h (new_allocator::allocate): Add
            static assertion for LWG 3307.
            * testsuite/20_util/allocator/void.cc: Check that converting
            constructor is noexcept. Check for propagation traits and
            size_type and difference_type. Check that pointer and
            const_pointer are gone in C++20.
diff mbox series

Patch

diff --git a/libstdc++-v3/include/bits/allocator.h b/libstdc++-v3/include/bits/allocator.h
index c5c1f28b3d0..73d5d7a25be 100644
--- a/libstdc++-v3/include/bits/allocator.h
+++ b/libstdc++-v3/include/bits/allocator.h
@@ -60,6 +60,13 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
    *  @{
    */
 
+  // Since C++20 the primary template should be used for allocator<void>,
+  // but then it would have a non-trivial default ctor and dtor, which
+  // would be an ABI change. So C++20 still uses the allocator<void> explicit
+  // specialization, with the historical ABI properties, but with the same
+  // members that are present in the primary template.
+
+#if ! _GLIBCXX_INLINE_VERSION
   /// allocator<void> specialization.
   template<>
     class allocator<void>
@@ -68,28 +75,40 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       typedef void        value_type;
       typedef size_t      size_type;
       typedef ptrdiff_t   difference_type;
+
 #if __cplusplus <= 201703L
+      // These were removed for C++20.
       typedef void*       pointer;
       typedef const void* const_pointer;
 
       template<typename _Tp1>
 	struct rebind
 	{ typedef allocator<_Tp1> other; };
-#else
-      allocator() = default;
+#endif
 
-      template<typename _Up>
-	constexpr
-	allocator(const allocator<_Up>&) { }
-#endif // ! C++20
-
-#if __cplusplus >= 201103L && __cplusplus <= 201703L
+#if __cplusplus >= 201103L
       // _GLIBCXX_RESOLVE_LIB_DEFECTS
       // 2103. std::allocator propagate_on_container_move_assignment
       typedef true_type propagate_on_container_move_assignment;
 
       typedef true_type is_always_equal;
 
+#if __cplusplus >= 202002L
+      allocator() = default;
+
+      template<typename _Up>
+	constexpr
+	allocator(const allocator<_Up>&) noexcept { }
+
+      // No allocate member because it's ill-formed by LWG 3307.
+      // No deallocate member because it would be undefined to call it
+      // with any pointer which wasn't obtained from allocate.
+
+#else // ! C++20
+    private:
+      // This uses construct and destroy in C++11/14/17 modes.
+      friend allocator_traits<allocator<void>>;
+
       template<typename _Up, typename... _Args>
 	void
 	construct(_Up* __p, _Args&&... __args)
@@ -101,11 +120,14 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	destroy(_Up* __p)
 	noexcept(std::is_nothrow_destructible<_Up>::value)
 	{ __p->~_Up(); }
-#endif // C++11 to C++17
+#endif // C++17
+#endif // C++11
+
     };
+#endif // ! _GLIBCXX_INLINE_VERSION
 
   /**
-   * @brief  The @a standard allocator, as per [20.4].
+   * @brief  The @a standard allocator, as per C++03 [20.4.1].
    *
    *  See https://gcc.gnu.org/onlinedocs/libstdc++/manual/memory.html#std.util.memory.allocator
    *  for further details.
@@ -119,7 +141,9 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       typedef _Tp        value_type;
       typedef size_t     size_type;
       typedef ptrdiff_t  difference_type;
+
 #if __cplusplus <= 201703L
+      // These were removed for C++20.
       typedef _Tp*       pointer;
       typedef const _Tp* const_pointer;
       typedef _Tp&       reference;
diff --git a/libstdc++-v3/include/bits/memoryfwd.h b/libstdc++-v3/include/bits/memoryfwd.h
index 10a386c69d2..b0f0307eb7b 100644
--- a/libstdc++-v3/include/bits/memoryfwd.h
+++ b/libstdc++-v3/include/bits/memoryfwd.h
@@ -63,15 +63,16 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<typename>
     class allocator;
 
-#if __cplusplus <= 201703L
   template<>
     class allocator<void>;
-#endif
 
 #if __cplusplus >= 201103L
-  /// Declare uses_allocator so it can be specialized in \<queue\> etc.
+  /// Declare uses_allocator so it can be specialized in `<queue>` etc.
   template<typename, typename>
     struct uses_allocator;
+
+  template<typename>
+    struct allocator_traits;
 #endif
 
   /// @} group memory
diff --git a/libstdc++-v3/include/ext/malloc_allocator.h b/libstdc++-v3/include/ext/malloc_allocator.h
index 3112f7f52bd..1e90b179f53 100644
--- a/libstdc++-v3/include/ext/malloc_allocator.h
+++ b/libstdc++-v3/include/ext/malloc_allocator.h
@@ -99,9 +99,15 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       // NB: __n is permitted to be 0.  The C++ standard says nothing
       // about what the return value is when __n == 0.
-      _Tp*
+      _GLIBCXX_NODISCARD _Tp*
       allocate(size_type __n, const void* = 0)
       {
+#if __cplusplus >= 201103L
+	 // _GLIBCXX_RESOLVE_LIB_DEFECTS
+	 // 3308. std::allocator<void>().allocate(n)
+	 static_assert(sizeof(_Tp) != 0, "cannot allocate incomplete types");
+#endif
+
 	if (__builtin_expect(__n > this->_M_max_size(), false))
 	  {
 	    // _GLIBCXX_RESOLVE_LIB_DEFECTS
diff --git a/libstdc++-v3/include/ext/new_allocator.h b/libstdc++-v3/include/ext/new_allocator.h
index 9e624ba8b50..3fb893be152 100644
--- a/libstdc++-v3/include/ext/new_allocator.h
+++ b/libstdc++-v3/include/ext/new_allocator.h
@@ -42,7 +42,7 @@  namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
   /**
-   *  @brief  An allocator that uses global new, as per [20.4].
+   *  @brief  An allocator that uses global new, as per C++03 [20.4.1].
    *  @ingroup allocators
    *
    *  This is precisely the allocator defined in the C++ Standard.
@@ -102,6 +102,12 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       _GLIBCXX_NODISCARD _Tp*
       allocate(size_type __n, const void* = static_cast<const void*>(0))
       {
+#if __cplusplus >= 201103L
+	 // _GLIBCXX_RESOLVE_LIB_DEFECTS
+	 // 3308. std::allocator<void>().allocate(n)
+	 static_assert(sizeof(_Tp) != 0, "cannot allocate incomplete types");
+#endif
+
 	if (__builtin_expect(__n > this->_M_max_size(), false))
 	  {
 	    // _GLIBCXX_RESOLVE_LIB_DEFECTS
diff --git a/libstdc++-v3/testsuite/20_util/allocator/void.cc b/libstdc++-v3/testsuite/20_util/allocator/void.cc
index a172606e174..e3d024d525b 100644
--- a/libstdc++-v3/testsuite/20_util/allocator/void.cc
+++ b/libstdc++-v3/testsuite/20_util/allocator/void.cc
@@ -33,6 +33,18 @@  test01()
   std::allocator_traits<alloc_type>::destroy(a, &i);
 }
 
+static_assert( std::allocator<void>::propagate_on_container_move_assignment(),
+	       "POCMA trait should always be present" );
+static_assert( std::allocator<void>::is_always_equal(),
+	       "is_always_equal trait should always be present" );
+
+static_assert(
+    std::is_same<std::allocator<void>::size_type, std::size_t>(),
+    "size_type is size_t" );
+static_assert(
+    std::is_same<std::allocator<void>::difference_type, std::ptrdiff_t>(),
+    "size_type is size_t" );
+
 // These properties are formally unspecified, but have always been true for
 // the libstdc++ definition of allocator<void>.
 static_assert(
@@ -48,12 +60,32 @@  static_assert(
     std::is_trivially_destructible<std::allocator<void>>::value,
     "explicit specialization has trivial destructor");
 
-#if __cplusplus > 201703L
+#if __cplusplus >= 202002L
 // C++20 removes the allocator<void> explicit specialization, so it can now be
 // constructed using the converting constructor from other specializations.
-static_assert( std::is_constructible_v<std::allocator<void>,
-				       std::allocator<int>> );
-#endif
+static_assert( std::is_nothrow_constructible_v<std::allocator<void>,
+					       std::allocator<int>> );
+
+template<typename T>
+concept has_pointer = requires { typename T::pointer; };
+template<typename T>
+concept has_const_pointer = requires { typename T::const_pointer; };
+template<typename T>
+concept has_size_type = requires { typename T::size_type; };
+template<typename T>
+concept has_difference_type = requires { typename T::difference_type; };
+
+// These were removed for C++20
+static_assert( ! has_pointer<std::allocator<void>> );
+static_assert( ! has_const_pointer<std::allocator<void>> );
+
+#else
+static_assert(
+    std::is_same<std::allocator<void>::pointer, void*>(),
+    "pointer is void*" );
+static_assert( std::is_same<std::allocator<void>::const_pointer, const void*>(),
+    "const_pointer is const void*" );
+#endif // C++20
 
 int
 main()