https://gcc.gnu.org/g:e4f550457b0b5c9c87356f6dfc60ae980e319585

commit r17-4556-ge4f550457b0b5c9c87356f6dfc60ae980e319585
Author: Patrick Palka <[email protected]>
Date:   Thu Sep 17 09:26:02 2026 +0200

    libstdc++: Fix SFINAE with shared_ptr and invalid deleter [PR126120]
    
    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]>
    Reviewed-by: Jonathan Wakely <[email protected]>

Diff:
---
 libstdc++-v3/include/bits/shared_ptr.h             | 22 ++++++++++-
 libstdc++-v3/include/bits/shared_ptr_base.h        | 40 ++++++++++++-------
 .../testsuite/20_util/shared_ptr/cons/126120.cc    | 45 ++++++++++++++++++++++
 3 files changed, 91 insertions(+), 16 deletions(-)

diff --git a/libstdc++-v3/include/bits/shared_ptr.h 
b/libstdc++-v3/include/bits/shared_ptr.h
index 8be9aace1711..99cedf12d436 100644
--- a/libstdc++-v3/include/bits/shared_ptr.h
+++ b/libstdc++-v3/include/bits/shared_ptr.h
@@ -102,6 +102,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
   /// @cond undocumented
 
+  // Used as parameter type when constraining nullptr constructor
+  struct __shared_nullptr_ctor_tag
+  {
+    constexpr explicit
+    __shared_nullptr_ctor_tag(__shared_nullptr_ctor_tag*)
+    { }
+  };
+
   // Constraint for overloads taking non-array types.
 #if __cpp_concepts && __glibcxx_type_trait_variable_templates
   template<typename _Tp>
@@ -166,6 +174,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 +225,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 +242,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(nullptr))
         : __shared_ptr<_Tp>(__p, std::move(__d)) { }
 
       /**
@@ -265,7 +282,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(nullptr))
        : __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 c8342b7904a5..b67a697c6df6 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 000000000000..7a82358200e3
--- /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

Reply via email to