]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Fix std::allocator_traits::construct constraints [PR108619]
authorJonathan Wakely <jwakely@redhat.com>
Wed, 10 Jul 2024 22:14:19 +0000 (23:14 +0100)
committerJonathan Wakely <redi@gcc.gnu.org>
Fri, 23 Aug 2024 12:39:36 +0000 (13:39 +0100)
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
libstdc++-v3/include/bits/new_allocator.h
libstdc++-v3/include/ext/malloc_allocator.h
libstdc++-v3/include/std/type_traits
libstdc++-v3/testsuite/20_util/allocator/89510.cc
libstdc++-v3/testsuite/20_util/allocator_traits/members/108619.cc [new file with mode: 0644]
libstdc++-v3/testsuite/ext/malloc_allocator/89510.cc
libstdc++-v3/testsuite/ext/new_allocator/89510.cc

index 82fc79c7b9f9d048ba9657c41e23f192c5b07df3..c2acc2ab207075fad91c7dd367bc78974b744b68 100644 (file)
@@ -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
index 5dcdee11c4dd34f2562200c822f935af4b38b4a0..3a749dc91dbb4bf4a2e0094526f5ee884b026783 100644 (file)
@@ -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>
index 36513780925f7f56720ed97b3b10bcbf2ffbe5f4..2a58847b8a952d0c8f0e6bf72a0de73edf5e324e 100644 (file)
@@ -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>
index c39a37925376ef0280224c21ed3c05340def8157..7415e200e0976227a1880c3f79ccf08eff71b8d7 100644 (file)
@@ -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
index 95c85d2634ee779ea63c94995c5f28db597ea680..91526462a0966e52a2bb1ebfae3f33fe4ba978df 100644 (file)
@@ -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 (file)
index 0000000..01bf611
--- /dev/null
@@ -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);
+}
index 1889c88d6e5a7f1810cafa82bd032d5dd6b722e8..771facbdf7497edac77a6b559aa7e5c90041286a 100644 (file)
@@ -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)" );
index 06384ae55700388b441d8dcffaa037e0f3a869ba..7fc443831c96decf0c7cd62a0ed826a6a602c2a8 100644 (file)
@@ -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)" );