diff mbox series

libstdc++: Fix std::allocator_traits::construct constraints [PR108619]

Message ID 20240801213927.388966-2-jwakely@redhat.com
State New
Headers show
Series libstdc++: Fix std::allocator_traits::construct constraints [PR108619] | expand

Commit Message

Jonathan Wakely Aug. 1, 2024, 9:32 p.m. UTC
The latest in the series of "rewrite all our enable_if-based overload
sets using concepts!" improvements.

A smaller change will be needed on the branches, this is just for trunk.

Tested x86_64-linux.

-- >8 --

Using std::is_constructible in the constraints introduces a spurious
dependency on the type being destructible, which should not be required
for constructing with an allocator. The test case shows a case where the
type has a private destructor, which can be destroyed by the allocator,
but std::is_destructible and std::is_constructible are false.

Similarly, using is_nothrow_constructible in the noexcept-specifiers
for the construct members of allocator_traits and std::allocator,
__gnu_cxx::__new_allocator, and __gnu_cxx::__malloc_allocator gives the
wrong answer if the type isn't destructible.
We need a new type trait to define those correctly, so that we only
check if the placement new-expression is nothrow after using
is_constructible to check that it would be well-formed.

Instead of just fixing the overly restrictive constraint to check for
placement new, rewrite allocator_traits in terms of 'if constexpr' using
variable templates and the detection idiom.

Although we can use 'if constexpr' and variable templates in C++11 with
appropriate uses of diagnostic pragmas, we can't have constexpr
functions with multiple return statements. This means that in C++11 mode
the _S_nothrow_construct and _S_nothrow_destroy helpers used for
noexcept-specifiers still need to be overlaods using enable_if. Nearly
everything else can be simplified to reduce overload resolution and
enable_if checks.

libstdc++-v3/ChangeLog:

	PR libstdc++/108619
	* include/bits/alloc_traits.h (__allocator_traits_base): Add
	variable templates for detecting which allocator operations are
	supported.
	(allocator_traits): Use 'if constexpr' instead of dispatching to
	overloads constrained with enable_if.
	(allocator_traits<allocator<T>>::construct): Use Construct if
	construct_at is not supported. Use
	__is_nothrow_new_constructible for noexcept-specifier.
	(allocator_traits<allocator<void>>::construct): Use
	__is_nothrow_new_constructible for noexcept-specifier.
	* include/bits/new_allocator.h (construct): Likewise.
	* include/ext/malloc_allocator.h (construct): Likewise.
	* include/std/type_traits (__is_nothrow_new_constructible): New
	variable template.
	* testsuite/20_util/allocator/89510.cc: Adjust expected results.
	* testsuite/ext/malloc_allocator/89510.cc: Likewise.
	* testsuite/ext/new_allocator/89510.cc: Likewise.
	* testsuite/20_util/allocator_traits/members/108619.cc: New test.
---
 libstdc++-v3/include/bits/alloc_traits.h      | 351 ++++++++++++------
 libstdc++-v3/include/bits/new_allocator.h     |   2 +-
 libstdc++-v3/include/ext/malloc_allocator.h   |   2 +-
 libstdc++-v3/include/std/type_traits          |  15 +
 .../testsuite/20_util/allocator/89510.cc      |  14 +-
 .../allocator_traits/members/108619.cc        |  35 ++
 .../testsuite/ext/malloc_allocator/89510.cc   |  14 +-
 .../testsuite/ext/new_allocator/89510.cc      |  14 +-
 8 files changed, 314 insertions(+), 133 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/20_util/allocator_traits/members/108619.cc
diff mbox series

Patch

diff --git a/libstdc++-v3/include/bits/alloc_traits.h b/libstdc++-v3/include/bits/alloc_traits.h
index 82fc79c7b9f..c2acc2ab207 100644
--- a/libstdc++-v3/include/bits/alloc_traits.h
+++ b/libstdc++-v3/include/bits/alloc_traits.h
@@ -48,10 +48,19 @@  namespace std _GLIBCXX_VISIBILITY(default)
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
 #if __cplusplus >= 201103L
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wc++14-extensions" // for variable templates
+#pragma GCC diagnostic ignored "-Wc++17-extensions" // for if-constexpr
+
   /// @cond undocumented
   struct __allocator_traits_base
   {
+#if __cpp_concepts
+    template<typename _Tp, typename _Up>
+#else
     template<typename _Tp, typename _Up, typename = void>
+#endif
       struct __rebind : __replace_first_arg<_Tp, _Up>
       {
 	static_assert(is_same<
@@ -61,8 +70,13 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       };
 
     template<typename _Tp, typename _Up>
+#if __cpp_concepts
+      requires requires { typename _Tp::template rebind<_Up>::other; }
+      struct __rebind<_Tp, _Up>
+#else
       struct __rebind<_Tp, _Up,
 		      __void_t<typename _Tp::template rebind<_Up>::other>>
+#endif
       {
 	using type = typename _Tp::template rebind<_Up>::other;
 
@@ -89,6 +103,135 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       using __pocs = typename _Tp::propagate_on_container_swap;
     template<typename _Tp>
       using __equal = __type_identity<typename _Tp::is_always_equal>;
+
+    // __has_allocate_hint is true if a.allocate(n, hint) is well-formed.
+#if __cpp_concepts
+    template<typename _Alloc, typename _Sz, typename _Vp>
+      static constexpr bool __has_allocate_hint
+	= requires (_Alloc& __a, _Sz __n, _Vp __hint) {
+	__a.allocate(__n, __hint);
+      };
+#else
+    template<typename _Alloc, typename _Sz, typename _Vp>
+      using __allocate_hint_t
+	= decltype(std::declval<_Alloc&>()
+		     .allocate(std::declval<_Sz>(), std::declval<_Vp>()));
+    template<typename _Alloc, typename _Sz, typename _Vp, typename = void>
+      static constexpr bool __has_allocate_hint = false;
+    template<typename _Alloc, typename _Sz, typename _Vp>
+      static constexpr bool
+      __has_allocate_hint<_Alloc, _Sz, _Vp,
+			  __void_t<__allocate_hint_t<_Alloc, _Sz, _Vp>>>
+	= true;
+#endif
+
+    // __has_construct is true if a.construct(p, args...) is well-formed.
+    // __can_construct is true if either __has_construct is true, or if
+    // a placement new-expression for T(args...) is well-formed. We use this
+    // to constrain allocator_traits::construct, as a libstdc++ extension.
+#if __cpp_concepts
+    template<typename _Alloc, typename _Tp, typename... _Args>
+      static constexpr bool __has_construct
+	= requires (_Alloc& __a, _Tp* __p, _Args&&... __args) {
+	  __a.construct(__p, std::forward<_Args>(__args)...);
+	};
+    template<typename _Tp, typename... _Args>
+      static constexpr bool __can_construct_at
+	= requires (_Tp* __p, _Args&&... __args) {
+#if __cpp_constexpr_dynamic_alloc
+	  std::construct_at(__p, std::forward<_Args>(__args)...);
+#else
+	  ::new((void*)__p) _Tp(std::forward<_Args>(__args)...);
+#endif
+	};
+    template<typename _Alloc, typename _Tp, typename... _Args>
+      static constexpr bool __can_construct
+	= __has_construct<_Alloc, _Tp, _Args...>
+	    || __can_construct_at<_Tp, _Args...>;
+#else
+    template<typename _Alloc, typename _Tp, typename... _Args>
+      using __construct_t
+	= decltype(std::declval<_Alloc&>().construct(std::declval<_Tp*>(),
+						     std::declval<_Args>()...));
+    template<typename _Alloc, typename _Tp, typename, typename... _Args>
+      static constexpr bool __has_construct_impl = false;
+    template<typename _Alloc, typename _Tp, typename... _Args>
+      static constexpr bool
+      __has_construct_impl<_Alloc, _Tp,
+			   __void_t<__construct_t<_Alloc, _Tp, _Args...>>,
+			   _Args...>
+	= true;
+    template<typename _Alloc, typename _Tp, typename... _Args>
+      static constexpr bool __has_construct
+	= __has_construct_impl<_Alloc, _Tp, void, _Args...>;
+    template<typename _Tp, typename... _Args>
+      using __new_expr_t
+	= decltype(::new((void*)0) _Tp(std::declval<_Args>()...));
+    template<typename _Tp, typename, typename... _Args>
+      static constexpr bool __has_new_expr = false;
+    template<typename _Tp, typename... _Args>
+      static constexpr bool
+      __has_new_expr<_Tp, __void_t<__new_expr_t<_Tp, _Args...>>, _Args...>
+	= true;
+    template<typename _Alloc, typename _Tp, typename... _Args>
+      static constexpr bool __can_construct
+	= __has_construct<_Alloc, _Tp, _Args...>
+	    || __has_new_expr<_Tp, void, _Args...>;
+#endif
+
+    // __has_destroy is true if a.destroy(p) is well-formed.
+#if __cpp_concepts
+    template<typename _Alloc, typename _Tp>
+      static constexpr bool __has_destroy = requires (_Alloc& __a, _Tp* __p) {
+	__a.destroy(__p);
+      };
+#else
+    template<typename _Alloc, typename _Tp>
+      using __destroy_t
+	= decltype(std::declval<_Alloc&>().destroy(std::declval<_Tp*>()));
+    template<typename _Alloc, typename _Tp, typename = void>
+      static constexpr bool __has_destroy = false;
+    template<typename _Alloc, typename _Tp>
+      static constexpr bool __has_destroy<_Alloc, _Tp,
+					  __void_t<__destroy_t<_Alloc, _Tp>>>
+	= true;
+#endif
+
+    // __has_max_size is true if a.max_size() is well-formed.
+#if __cpp_concepts
+    template<typename _Alloc>
+      static constexpr bool __has_max_size = requires (const _Alloc& __a) {
+	__a.max_size();
+      };
+#else
+    template<typename _Alloc>
+      using __max_size_t = decltype(std::declval<const _Alloc&>().max_size());
+    template<typename _Alloc, typename = void>
+      static constexpr bool __has_max_size = false;
+    template<typename _Alloc>
+      static constexpr bool __has_max_size<_Alloc,
+					   __void_t<__max_size_t<_Alloc>>>
+	= true;
+#endif
+
+    // __has_soccc is true if a.select_on_container_copy_construction()
+    // is well-formed.
+#if __cpp_concepts
+    template<typename _Alloc>
+      static constexpr bool __has_soccc = requires (const _Alloc& __a) {
+	__a.select_on_container_copy_construction();
+      };
+#else
+    template<typename _Alloc>
+      using __soccc_t
+	= decltype(std::declval<const _Alloc&>()
+		     .select_on_container_copy_construction());
+    template<typename _Alloc, typename = void>
+      static constexpr bool __has_soccc = false;
+    template<typename _Alloc>
+      static constexpr bool __has_soccc<_Alloc, __void_t<__soccc_t<_Alloc>>>
+	= true;
+#endif
   };
 
   template<typename _Alloc, typename _Up>
@@ -230,98 +373,6 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename _Tp>
 	using rebind_traits = allocator_traits<rebind_alloc<_Tp>>;
 
-    private:
-      template<typename _Alloc2>
-	static constexpr auto
-	_S_allocate(_Alloc2& __a, size_type __n, const_void_pointer __hint, int)
-	-> decltype(__a.allocate(__n, __hint))
-	{ return __a.allocate(__n, __hint); }
-
-      template<typename _Alloc2>
-	static constexpr pointer
-	_S_allocate(_Alloc2& __a, size_type __n, const_void_pointer, ...)
-	{ return __a.allocate(__n); }
-
-      template<typename _Tp, typename... _Args>
-	struct __construct_helper
-	{
-	  template<typename _Alloc2,
-	    typename = decltype(std::declval<_Alloc2*>()->construct(
-		  std::declval<_Tp*>(), std::declval<_Args>()...))>
-	    static true_type __test(int);
-
-	  template<typename>
-	    static false_type __test(...);
-
-	  using type = decltype(__test<_Alloc>(0));
-	};
-
-      template<typename _Tp, typename... _Args>
-	using __has_construct
-	  = typename __construct_helper<_Tp, _Args...>::type;
-
-      template<typename _Tp, typename... _Args>
-	static _GLIBCXX14_CONSTEXPR _Require<__has_construct<_Tp, _Args...>>
-	_S_construct(_Alloc& __a, _Tp* __p, _Args&&... __args)
-	noexcept(noexcept(__a.construct(__p, std::forward<_Args>(__args)...)))
-	{ __a.construct(__p, std::forward<_Args>(__args)...); }
-
-      template<typename _Tp, typename... _Args>
-	static _GLIBCXX14_CONSTEXPR
-	_Require<__and_<__not_<__has_construct<_Tp, _Args...>>,
-			       is_constructible<_Tp, _Args...>>>
-	_S_construct(_Alloc&, _Tp* __p, _Args&&... __args)
-	noexcept(std::is_nothrow_constructible<_Tp, _Args...>::value)
-	{
-#if __cplusplus <= 201703L
-	  ::new((void*)__p) _Tp(std::forward<_Args>(__args)...);
-#else
-	  std::construct_at(__p, std::forward<_Args>(__args)...);
-#endif
-	}
-
-      template<typename _Alloc2, typename _Tp>
-	static _GLIBCXX14_CONSTEXPR auto
-	_S_destroy(_Alloc2& __a, _Tp* __p, int)
-	noexcept(noexcept(__a.destroy(__p)))
-	-> decltype(__a.destroy(__p))
-	{ __a.destroy(__p); }
-
-      template<typename _Alloc2, typename _Tp>
-	static _GLIBCXX14_CONSTEXPR void
-	_S_destroy(_Alloc2&, _Tp* __p, ...)
-	noexcept(std::is_nothrow_destructible<_Tp>::value)
-	{ std::_Destroy(__p); }
-
-      template<typename _Alloc2>
-	static constexpr auto
-	_S_max_size(_Alloc2& __a, int)
-	-> decltype(__a.max_size())
-	{ return __a.max_size(); }
-
-      template<typename _Alloc2>
-	static constexpr size_type
-	_S_max_size(_Alloc2&, ...)
-	{
-	  // _GLIBCXX_RESOLVE_LIB_DEFECTS
-	  // 2466. allocator_traits::max_size() default behavior is incorrect
-	  return __gnu_cxx::__numeric_traits<size_type>::__max
-	    / sizeof(value_type);
-	}
-
-      template<typename _Alloc2>
-	static constexpr auto
-	_S_select(_Alloc2& __a, int)
-	-> decltype(__a.select_on_container_copy_construction())
-	{ return __a.select_on_container_copy_construction(); }
-
-      template<typename _Alloc2>
-	static constexpr _Alloc2
-	_S_select(_Alloc2& __a, ...)
-	{ return __a; }
-
-    public:
-
       /**
        *  @brief  Allocate memory.
        *  @param  __a  An allocator.
@@ -346,7 +397,12 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       */
       _GLIBCXX_NODISCARD static _GLIBCXX20_CONSTEXPR pointer
       allocate(_Alloc& __a, size_type __n, const_void_pointer __hint)
-      { return _S_allocate(__a, __n, __hint, 0); }
+      {
+	if constexpr (__has_allocate_hint<_Alloc, size_type, const_void_pointer>)
+	  return __a.allocate(__n, __hint);
+	else
+	  return __a.allocate(__n);
+      }
 
       /**
        *  @brief  Deallocate memory.
@@ -372,12 +428,20 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
        *  arguments @a __args...
       */
       template<typename _Tp, typename... _Args>
-	static _GLIBCXX20_CONSTEXPR auto
+#if __cpp_concepts && __cpp_constexpr_dynamic_alloc
+	requires __can_construct<_Alloc, _Tp, _Args...>
+	static constexpr void
+#else
+	static __enable_if_t<__can_construct<_Alloc, _Tp, _Args...>>
+#endif
 	construct(_Alloc& __a, _Tp* __p, _Args&&... __args)
-	noexcept(noexcept(_S_construct(__a, __p,
-				       std::forward<_Args>(__args)...)))
-	-> decltype(_S_construct(__a, __p, std::forward<_Args>(__args)...))
-	{ _S_construct(__a, __p, std::forward<_Args>(__args)...); }
+	noexcept(_S_nothrow_construct<_Tp, _Args...>())
+	{
+	  if constexpr (__has_construct<_Alloc, _Tp, _Args...>)
+	    __a.construct(__p, std::forward<_Args>(__args)...);
+	  else
+	    std::_Construct(__p, std::forward<_Args>(__args)...);
+	}
 
       /**
        *  @brief  Destroy an object of type @a _Tp
@@ -390,8 +454,13 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename _Tp>
 	static _GLIBCXX20_CONSTEXPR void
 	destroy(_Alloc& __a, _Tp* __p)
-	noexcept(noexcept(_S_destroy(__a, __p, 0)))
-	{ _S_destroy(__a, __p, 0); }
+	noexcept(_S_nothrow_destroy<_Tp>())
+	{
+	  if constexpr (__has_destroy<_Alloc, _Tp>)
+	    __a.destroy(__p);
+	  else
+	    std::_Destroy(__p);
+	}
 
       /**
        *  @brief  The maximum supported allocation size
@@ -403,7 +472,15 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       */
       static _GLIBCXX20_CONSTEXPR size_type
       max_size(const _Alloc& __a) noexcept
-      { return _S_max_size(__a, 0); }
+      {
+	if constexpr (__has_max_size<_Alloc>)
+	  return __a.max_size();
+	else
+	  // _GLIBCXX_RESOLVE_LIB_DEFECTS
+	  // 2466. allocator_traits::max_size() default behavior is incorrect
+	  return __gnu_cxx::__numeric_traits<size_type>::__max
+	    / sizeof(value_type);
+      }
 
       /**
        *  @brief  Obtain an allocator to use when copying a container.
@@ -415,8 +492,61 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       */
       static _GLIBCXX20_CONSTEXPR _Alloc
       select_on_container_copy_construction(const _Alloc& __rhs)
-      { return _S_select(__rhs, 0); }
+      {
+	if constexpr (__has_soccc<_Alloc>)
+	  return __rhs.select_on_container_copy_construction();
+	else
+	  return __rhs;
+      }
+
+    private:
+#if __cpp_constexpr >= 201304 // >= C++14
+      template<typename _Tp, typename... _Args>
+	static constexpr bool
+	_S_nothrow_construct(_Alloc* __a = nullptr, _Tp* __p = nullptr)
+	{
+	  if constexpr (__has_construct<_Alloc, _Tp, _Args...>)
+	    return noexcept(__a->construct(__p, std::declval<_Args>()...));
+	  else
+	    return __is_nothrow_new_constructible<_Tp, _Args...>;
+	}
+
+      template<typename _Tp>
+	static constexpr bool
+	_S_nothrow_destroy(_Alloc* __a = nullptr, _Tp* __p = nullptr)
+	{
+	  if constexpr (__has_destroy<_Alloc, _Tp>)
+	    return noexcept(__a->destroy(__p));
+	  else
+	    return is_nothrow_destructible<_Tp>::value;
+	}
+#else
+      template<typename _Tp, typename... _Args>
+	static constexpr
+	__enable_if_t<__has_construct<_Alloc, _Tp, _Args...>, bool>
+	_S_nothrow_construct(_Alloc* __a = nullptr, _Tp* __p = nullptr)
+	{ return noexcept(__a->construct(__p, std::declval<_Args>()...)); }
+
+      template<typename _Tp, typename... _Args>
+	static constexpr
+	__enable_if_t<!__has_construct<_Alloc, _Tp, _Args...>, bool>
+	_S_nothrow_construct(_Alloc* = nullptr, _Tp* __p = nullptr)
+	{ return __is_nothrow_new_constructible<_Tp, _Args...>; }
+
+      template<typename _Tp>
+	static constexpr
+	__enable_if_t<__has_destroy<_Alloc, _Tp>, bool>
+	_S_nothrow_destroy(_Alloc* __a = nullptr, _Tp* __p = nullptr)
+	{ return noexcept(__a->destroy(__p)); }
+
+      template<typename _Tp>
+	static constexpr
+	__enable_if_t<!__has_destroy<_Alloc, _Tp>, bool>
+	_S_nothrow_destroy(_Alloc* = nullptr, _Tp* __p = nullptr)
+	{ return is_nothrow_destructible<_Tp>::value; }
+#endif
     };
+#pragma GCC diagnostic pop
 
 #if _GLIBCXX_HOSTED
   /// Partial specialization for std::allocator.
@@ -526,14 +656,20 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename _Up, typename... _Args>
 	[[__gnu__::__always_inline__]]
 	static _GLIBCXX20_CONSTEXPR void
-	construct(allocator_type& __a __attribute__((__unused__)), _Up* __p,
-		  _Args&&... __args)
-	noexcept(std::is_nothrow_constructible<_Up, _Args...>::value)
+	construct(allocator_type& __a __attribute__((__unused__)),
+		  _Up* __p, _Args&&... __args)
+#if __cplusplus <= 201703L
+	noexcept(noexcept(__a.construct(__p, std::forward<_Args>(__args)...)))
+#else
+	noexcept(__is_nothrow_new_constructible<_Up, _Args...>)
+#endif
 	{
 #if __cplusplus <= 201703L
 	  __a.construct(__p, std::forward<_Args>(__args)...);
-#else
+#elif __cpp_constexpr_dynamic_alloc // >= C++20
 	  std::construct_at(__p, std::forward<_Args>(__args)...);
+#else
+	  std::_Construct(__p, std::forward<_Args>(__args)...);
 #endif
 	}
 
@@ -653,7 +789,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	[[__gnu__::__always_inline__]]
 	static _GLIBCXX20_CONSTEXPR void
 	construct(allocator_type&, _Up* __p, _Args&&... __args)
-	noexcept(std::is_nothrow_constructible<_Up, _Args...>::value)
+	noexcept(__is_nothrow_new_constructible<_Up, _Args...>)
 	{ std::_Construct(__p, std::forward<_Args>(__args)...); }
 
       /**
@@ -944,6 +1080,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       std::_Destroy(__first, __last);
     }
 #endif
+
   /// @endcond
 
 _GLIBCXX_END_NAMESPACE_VERSION
diff --git a/libstdc++-v3/include/bits/new_allocator.h b/libstdc++-v3/include/bits/new_allocator.h
index 5dcdee11c4d..3a749dc91db 100644
--- a/libstdc++-v3/include/bits/new_allocator.h
+++ b/libstdc++-v3/include/bits/new_allocator.h
@@ -187,7 +187,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	__attribute__((__always_inline__))
 	void
 	construct(_Up* __p, _Args&&... __args)
-	noexcept(std::is_nothrow_constructible<_Up, _Args...>::value)
+	noexcept(__is_nothrow_new_constructible<_Up, _Args...>)
 	{ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
 
       template<typename _Up>
diff --git a/libstdc++-v3/include/ext/malloc_allocator.h b/libstdc++-v3/include/ext/malloc_allocator.h
index 36513780925..2a58847b8a9 100644
--- a/libstdc++-v3/include/ext/malloc_allocator.h
+++ b/libstdc++-v3/include/ext/malloc_allocator.h
@@ -161,7 +161,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename _Up, typename... _Args>
         void
         construct(_Up* __p, _Args&&... __args)
-	noexcept(std::is_nothrow_constructible<_Up, _Args...>::value)
+	noexcept(std::__is_nothrow_new_constructible<_Up, _Args...>)
 	{ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
 
       template<typename _Up>
diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits
index c39a3792537..7415e200e09 100644
--- a/libstdc++-v3/include/std/type_traits
+++ b/libstdc++-v3/include/std/type_traits
@@ -1643,6 +1643,21 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif
 #endif // __cpp_lib_is_nothrow_convertible
 
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wc++14-extensions" // for variable templates
+  template<typename _Tp, typename... _Args>
+    struct __is_nothrow_new_constructible_impl
+    : __bool_constant<
+	noexcept(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...))
+      >
+    { };
+
+  template<typename _Tp, typename... _Args>
+    _GLIBCXX17_INLINE constexpr bool __is_nothrow_new_constructible
+      = __and_<is_constructible<_Tp, _Args...>,
+	       __is_nothrow_new_constructible_impl<_Tp, _Args...>>::value;
+#pragma GCC diagnostic pop
+
   // Const-volatile modifications.
 
   /// remove_const
diff --git a/libstdc++-v3/testsuite/20_util/allocator/89510.cc b/libstdc++-v3/testsuite/20_util/allocator/89510.cc
index 95c85d2634e..91526462a09 100644
--- a/libstdc++-v3/testsuite/20_util/allocator/89510.cc
+++ b/libstdc++-v3/testsuite/20_util/allocator/89510.cc
@@ -136,13 +136,11 @@  struct Z
 };
 
 Z* zp;
-// These construct calls should be noexcept, but they are false because
-// they use is_nothrow_constructible which depends on is_nothrow_destructible.
 #if __cplusplus <= 201703L
-static_assert( ! noexcept(a.construct(zp)), "wrong" );
-static_assert( ! noexcept(a.construct(zp, 1)), "wrong" );
-static_assert( ! noexcept(a.destroy(zp)), "" );
+static_assert( noexcept(a.construct(zp)), "" );
+static_assert( noexcept(a.construct(zp, 1)), "" );
+static_assert( ! noexcept(a.destroy(zp)), "~Z is noexcept(false)" );
 #endif
-static_assert( ! noexcept(AT::construct(a, zp)), "" );
-static_assert( ! noexcept(AT::construct(a, zp, 1)), "" );
-static_assert( ! noexcept(AT::destroy(a, zp)), "" );
+static_assert( noexcept(AT::construct(a, zp)), "" );
+static_assert( noexcept(AT::construct(a, zp, 1)), "" );
+static_assert( ! noexcept(AT::destroy(a, zp)), "~Z is noexcept(false)" );
diff --git a/libstdc++-v3/testsuite/20_util/allocator_traits/members/108619.cc b/libstdc++-v3/testsuite/20_util/allocator_traits/members/108619.cc
new file mode 100644
index 00000000000..01bf611b804
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/allocator_traits/members/108619.cc
@@ -0,0 +1,35 @@ 
+// { dg-do compile { target c++11 } }
+
+#include <memory>
+
+template<typename T>
+struct Alloc
+{
+  Alloc() = default;
+
+  template<typename U> Alloc(const Alloc<U>&) { }
+
+  using value_type = T;
+
+  T* allocate(unsigned n)
+  { return std::allocator<T>().allocate(n); }
+
+  void deallocate(T* p, unsigned n)
+  { return std::allocator<T>().deallocate(p, n); }
+
+  template<typename U> void destroy(U* p){ p->~U(); }
+};
+
+
+class S
+{
+  ~S() = default;
+
+  friend Alloc<S>;
+};
+
+void
+test_pr108619(Alloc<int> a, S* p)
+{
+  std::allocator_traits<Alloc<int>>::construct(a, p);
+}
diff --git a/libstdc++-v3/testsuite/ext/malloc_allocator/89510.cc b/libstdc++-v3/testsuite/ext/malloc_allocator/89510.cc
index 1889c88d6e5..771facbdf74 100644
--- a/libstdc++-v3/testsuite/ext/malloc_allocator/89510.cc
+++ b/libstdc++-v3/testsuite/ext/malloc_allocator/89510.cc
@@ -137,13 +137,11 @@  struct Z
 };
 
 Z* zp;
-// These construct calls should be noexcept, but they are false because
-// they use is_nothrow_constructible which depends on is_nothrow_destructible.
 #if __cplusplus <= 201703L
-static_assert( ! noexcept(a.construct(zp)), "wrong" );
-static_assert( ! noexcept(a.construct(zp, 1)), "wrong" );
-static_assert( ! noexcept(a.destroy(zp)), "" );
+static_assert( noexcept(a.construct(zp)), "" );
+static_assert( noexcept(a.construct(zp, 1)), "" );
+static_assert( ! noexcept(a.destroy(zp)), "~Z is noexcept(false)" );
 #endif
-static_assert( ! noexcept(AT::construct(a, zp)), "" );
-static_assert( ! noexcept(AT::construct(a, zp, 1)), "" );
-static_assert( ! noexcept(AT::destroy(a, zp)), "" );
+static_assert( noexcept(AT::construct(a, zp)), "" );
+static_assert( noexcept(AT::construct(a, zp, 1)), "" );
+static_assert( ! noexcept(AT::destroy(a, zp)), "~Z is noexcept(false)" );
diff --git a/libstdc++-v3/testsuite/ext/new_allocator/89510.cc b/libstdc++-v3/testsuite/ext/new_allocator/89510.cc
index 06384ae5570..7fc443831c9 100644
--- a/libstdc++-v3/testsuite/ext/new_allocator/89510.cc
+++ b/libstdc++-v3/testsuite/ext/new_allocator/89510.cc
@@ -137,13 +137,11 @@  struct Z
 };
 
 Z* zp;
-// These construct calls should be noexcept, but they are false because
-// they use is_nothrow_constructible which depends on is_nothrow_destructible.
 #if __cplusplus <= 201703L
-static_assert( ! noexcept(a.construct(zp)), "wrong" );
-static_assert( ! noexcept(a.construct(zp, 1)), "wrong" );
-static_assert( ! noexcept(a.destroy(zp)), "" );
+static_assert( noexcept(a.construct(zp)), "" );
+static_assert( noexcept(a.construct(zp, 1)), "" );
+static_assert( ! noexcept(a.destroy(zp)), "~Z is noexcept(false)" );
 #endif
-static_assert( ! noexcept(AT::construct(a, zp)), "" );
-static_assert( ! noexcept(AT::construct(a, zp, 1)), "" );
-static_assert( ! noexcept(AT::destroy(a, zp)), "" );
+static_assert( noexcept(AT::construct(a, zp)), "" );
+static_assert( noexcept(AT::construct(a, zp, 1)), "" );
+static_assert( ! noexcept(AT::destroy(a, zp)), "~Z is noexcept(false)" );