From: Patrick Palka <[email protected]>

This implements the [util.smartptr.shared.const] constraints on the
constructors that take a deleter (which before C++20 were requirements
rather than constraints). In passing proactively implement the proposed
resolution of LWG 4110 which relaxes these constraints on the nullptr_t
overloads.

        PR libstdc++/126120

libstdc++-v3/ChangeLog:

        * include/bits/shared_ptr.h (std::__shared_nullptr_ctor_tag):
        Define.
        (shared_ptr::shared_ptr(nullptr_t, ...)): Constrain _Deleter
        via _NullptrDeleter parameter.
        * include/bits/shared_ptr_base.h (__shared_ptr::_ValidDeleter):
        New __enable_if_t constraint.
        (__shared_ptr::__shared_ptr(Yp*, _Deleter, ...)): Add _ValidDeleter
        constraint. Move static_assert checking deleter invocability
        to corresponding reset functions.
        (__shared_ptr::reset): Add static_assert checks for deleter
        invocability.
        * testsuite/20_util/shared_ptr/cons/126120.cc: New test.

Co-authored-by: Tomasz Kamiński <[email protected]>
---
v4 appies the review suggestion:

1. I have moved _ValidDeleter into __shared_ptr. However, it was also
used in shared_ptr(nullptr_t) constructors, and instead of making it
private, I have declared _NullptrDelete helper, separating the case
when non-void result of enable_if is needed.
The __shared_nullptr_ctor_tag (I proud of this name instead of 
__shared_ptr_nullptr_ctor_tag, so changing it is not negotiable)
is declared in global namespace, to avoid instantiating separate type
for each shared_ptr<T> specializations. It default constructor is
explicit, so shared_ptr<in>(nullptr, deleter, {}) remains ill-formed.**
(You couldn't deduce Alloc from braces, so other constructor was never
an candidate).

2. Removed _Constructible requirements on shared_ptr(nullptr_t, ...),
as the corresponding __shared_ptr constructor have no constraints.
I think it was leftover from previous version.

3. Moved static_assert from constructor to reset method.

Testing on x86_64-linux. *shared_ptr* test passed in all standard modes,
debug, assertions. OK for trunk when all test passes?

 libstdc++-v3/include/bits/shared_ptr.h        | 20 ++++++++-
 libstdc++-v3/include/bits/shared_ptr_base.h   | 40 +++++++++++------
 .../20_util/shared_ptr/cons/126120.cc         | 45 +++++++++++++++++++
 3 files changed, 89 insertions(+), 16 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/20_util/shared_ptr/cons/126120.cc

diff --git a/libstdc++-v3/include/bits/shared_ptr.h 
b/libstdc++-v3/include/bits/shared_ptr.h
index 8be9aace171..058d8456a43 100644
--- a/libstdc++-v3/include/bits/shared_ptr.h
+++ b/libstdc++-v3/include/bits/shared_ptr.h
@@ -102,6 +102,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
   /// @cond undocumented
 
+  // Used as parameter type when constraining nullptr constructor
+  struct __shared_nullptr_ctor_tag
+  {
+    explicit __shared_nullptr_ctor_tag() = default;
+  };
+
   // Constraint for overloads taking non-array types.
 #if __cpp_concepts && __glibcxx_type_trait_variable_templates
   template<typename _Tp>
@@ -166,6 +172,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
          is_assignable<__shared_ptr<_Tp>&, _Arg>::value, shared_ptr&
        >::type;
 
+      template<typename _Deleter>
+       using _NullptrDeleter = __enable_if_t<
+         __is_invocable<_Deleter&, nullptr_t&>::value,
+         __shared_nullptr_ctor_tag>;
+
     public:
 
       /// The type pointed to by the stored pointer, remove_extent_t<_Tp>
@@ -212,6 +223,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        shared_ptr(_Yp* __p, _Deleter __d)
         : __shared_ptr<_Tp>(__p, std::move(__d)) { }
 
+      // _GLIBCXX_RESOLVE_LIB_DEFECTS
+      // 4110. shared_ptr(nullptr_t, Deleter) is overconstrained...
+
       /**
        *  @brief  Construct a %shared_ptr that owns a null pointer
        *          and the deleter @a __d.
@@ -226,7 +240,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        *  The last owner will call __d(__p)
        */
       template<typename _Deleter>
-       shared_ptr(nullptr_t __p, _Deleter __d)
+       shared_ptr(nullptr_t __p, _Deleter __d,
+                  _NullptrDeleter<_Deleter> = __shared_nullptr_ctor_tag())
         : __shared_ptr<_Tp>(__p, std::move(__d)) { }
 
       /**
@@ -265,7 +280,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        *  The last owner will call __d(__p)
        */
       template<typename _Deleter, typename _Alloc>
-       shared_ptr(nullptr_t __p, _Deleter __d, _Alloc __a)
+       shared_ptr(nullptr_t __p, _Deleter __d, _Alloc __a,
+                  _NullptrDeleter<_Deleter> = __shared_nullptr_ctor_tag())
        : __shared_ptr<_Tp>(__p, std::move(__d), std::move(__a)) { }
 
       // Aliasing constructor
diff --git a/libstdc++-v3/include/bits/shared_ptr_base.h 
b/libstdc++-v3/include/bits/shared_ptr_base.h
index c8342b7904a..b67a697c6df 100644
--- a/libstdc++-v3/include/bits/shared_ptr_base.h
+++ b/libstdc++-v3/include/bits/shared_ptr_base.h
@@ -1504,6 +1504,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename _Yp, typename _Del>
        using _UniqAssignable = _UniqCompatible<_Yp, _Del, __shared_ptr&>;
 
+      // Constraint for well-formedness of deleter expression d(p):
+      template<typename _Deleter, typename _Up>
+       using _ValidDeleter = __enable_if_t<
+         __is_invocable<_Deleter&, _Up&>::value>;
+
     public:
 
 #if __cplusplus > 201402L
@@ -1524,24 +1529,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
          _M_enable_shared_from_this_with(__p);
        }
 
-      template<typename _Yp, typename _Deleter, typename = _SafeConv<_Yp>>
+      // _GLIBCXX_RESOLVE_LIB_DEFECTS
+      // 4110. shared_ptr(nullptr_t, Deleter) is overconstrained, breaking some
+      //       sensible deleters
+
+      template<typename _Yp, typename _Deleter, typename = _SafeConv<_Yp>,
+              typename = _ValidDeleter<_Deleter, _Yp*>>
        __shared_ptr(_Yp* __p, _Deleter __d)
        : _M_ptr(__p), _M_refcount(__p, std::move(__d))
-       {
-         static_assert(__is_invocable<_Deleter&, _Yp*&>::value,
-             "deleter expression d(p) is well-formed");
-         _M_enable_shared_from_this_with(__p);
-       }
+       { _M_enable_shared_from_this_with(__p); }
 
       template<typename _Yp, typename _Deleter, typename _Alloc,
-              typename = _SafeConv<_Yp>>
+              typename = _SafeConv<_Yp>, typename = _ValidDeleter<_Deleter, 
_Yp*>>
        __shared_ptr(_Yp* __p, _Deleter __d, _Alloc __a)
        : _M_ptr(__p), _M_refcount(__p, std::move(__d), std::move(__a))
-       {
-         static_assert(__is_invocable<_Deleter&, _Yp*&>::value,
-             "deleter expression d(p) is well-formed");
-         _M_enable_shared_from_this_with(__p);
-       }
+       { _M_enable_shared_from_this_with(__p); }
 
       template<typename _Deleter>
        __shared_ptr(nullptr_t __p, _Deleter __d)
@@ -1704,12 +1706,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename _Yp, typename _Deleter>
        _SafeConv<_Yp>
        reset(_Yp* __p, _Deleter __d)
-       { __shared_ptr(__p, std::move(__d)).swap(*this); }
+       {
+         static_assert(__is_invocable<_Deleter&, _Yp*&>::value,
+           "deleter expression d(p) is well-formed");
+
+         __shared_ptr(__p, std::move(__d)).swap(*this);
+       }
 
       template<typename _Yp, typename _Deleter, typename _Alloc>
        _SafeConv<_Yp>
        reset(_Yp* __p, _Deleter __d, _Alloc __a)
-        { __shared_ptr(__p, std::move(__d), std::move(__a)).swap(*this); }
+       {
+         static_assert(__is_invocable<_Deleter&, _Yp*&>::value,
+           "deleter expression d(p) is well-formed");
+
+         __shared_ptr(__p, std::move(__d), std::move(__a)).swap(*this);
+       }
 
       /// Return the stored pointer.
       element_type*
diff --git a/libstdc++-v3/testsuite/20_util/shared_ptr/cons/126120.cc 
b/libstdc++-v3/testsuite/20_util/shared_ptr/cons/126120.cc
new file mode 100644
index 00000000000..7a82358200e
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/shared_ptr/cons/126120.cc
@@ -0,0 +1,45 @@
+// PR libstdc++/126120 - is_constructible_v with shared_ptr from an invalid 
deleter
+// { dg-do compile { target c++11 } }
+
+#include <memory>
+#include <type_traits>
+
+struct NonDeleter { };
+
+struct NonMoveableDeleter
+{
+  NonMoveableDeleter() = default;
+  NonMoveableDeleter(const NonMoveableDeleter&) = delete;
+  void operator()(int* p) const { delete p; }
+};
+
+using shared_ptr = std::shared_ptr<int>;
+using Alloc = std::allocator<int>;
+
+// 1. Pointer + Deleter
+static_assert( ! std::is_constructible<shared_ptr, int*, NonDeleter>::value, 
"");
+static_assert( ! std::is_constructible<shared_ptr, int*, 
NonMoveableDeleter>::value, "");
+
+// 2. Pointer + Deleter + Allocator
+static_assert( ! std::is_constructible<shared_ptr, int*, NonDeleter, 
Alloc>::value, "");
+static_assert( ! std::is_constructible<shared_ptr, int*, NonMoveableDeleter, 
Alloc>::value, "");
+
+// 3. nullptr_t + Deleter
+static_assert( ! std::is_constructible<shared_ptr, std::nullptr_t, 
NonDeleter>::value, "");
+static_assert( ! std::is_constructible<shared_ptr, std::nullptr_t, 
NonMoveableDeleter>::value, "");
+
+// 4. nullptr_t + Deleter + Allocator
+static_assert( ! std::is_constructible<shared_ptr, std::nullptr_t, NonDeleter, 
Alloc>::value, "");
+static_assert( ! std::is_constructible<shared_ptr, std::nullptr_t, 
NonMoveableDeleter, Alloc>::value, "");
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wc++20-extensions" // lambda template
+void
+test_lwg4110()
+{
+  // LWG 4110 - shared_ptr(nullptr_t, Deleter) is overconstrained, breaking 
some sensible deleters
+  auto deleter = []<typename T>(T pointer) { delete pointer; };
+  shared_ptr p(new int, deleter);
+  shared_ptr q(new int, deleter, Alloc{});
+}
+#pragma GCC diagnostic pop
-- 
2.55.0

Reply via email to