https://gcc.gnu.org/g:8cf51d7516b92b352c358c14ab4e456ae53c3371

commit r15-3132-g8cf51d7516b92b352c358c14ab4e456ae53c3371
Author: Jonathan Wakely <jwak...@redhat.com>
Date:   Wed Jul 10 23:14:19 2024 +0100

    libstdc++: Fix std::allocator_traits::construct constraints [PR108619]
    
    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.

Diff:
---
 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 +
 libstdc++-v3/testsuite/20_util/allocator/89510.cc  |  14 +-
 .../20_util/allocator_traits/members/108619.cc     |  35 ++
 .../testsuite/ext/malloc_allocator/89510.cc        |  14 +-
 libstdc++-v3/testsuite/ext/new_allocator/89510.cc  |  14 +-
 8 files changed, 314 insertions(+), 133 deletions(-)

diff --git a/libstdc++-v3/include/bits/alloc_traits.h 
b/libstdc++-v3/include/bits/alloc_traits.h
index 82fc79c7b9f9..c2acc2ab2070 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 5dcdee11c4dd..3a749dc91dbb 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 36513780925f..2a58847b8a95 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 c39a37925376..7415e200e097 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 95c85d2634ee..91526462a096 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 000000000000..01bf611b8048
--- /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 1889c88d6e5a..771facbdf749 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 06384ae55700..7fc443831c96 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)" );

Reply via email to