[PATCH] libstdc++: Fix SFINAE with shared_ptr and invalid deleter [PR126120]
Patrick Palka
ppalka@redhat.com
Tue Aug 25 13:12:50 GMT 2026
On Tue, 25 Aug 2026, Tomasz Kaminski wrote:
>
>
> On Tue, Aug 25, 2026 at 6:11 AM Patrick Palka <ppalka@redhat.com> wrote:
> Tested on x86_64-pc-linux-gnu, does this look OK for trunk/16?
>
> -- >8 --
>
> This implements the [util.smartptr.shared.const] constraints on the
> constructors that take a deleter (which before C++20 were requirements
> rather than constraints).
>
> PR libstdc++/126120
>
> libstdc++-v3/ChangeLog:
>
> * include/bits/shared_ptr.h
> (shared_ptr::shared_ptr(nullptr_t, ...)): Add _Constructible
> constraint to verify __shared_ptr base initialization is
> well-formed.
> * include/bits/shared_ptr_base.h (_ValidDeleter): New __enable_if_t
> constraint.
> (__shared_ptr::__shared_ptr(..., _Deleter, ...)): Remove
> static_assert checking deleter invocability. Add _ValidDeleter
> constraint.
> * testsuite/20_util/shared_ptr/cons/126120.cc: New test.
> ---
> libstdc++-v3/include/bits/shared_ptr.h | 5 +--
> libstdc++-v3/include/bits/shared_ptr_base.h | 29 ++++++++---------
> .../20_util/shared_ptr/cons/126120.cc | 31 +++++++++++++++++++
> 3 files changed, 49 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 8be9aace1711..28b8a3ca8f29 100644
> --- a/libstdc++-v3/include/bits/shared_ptr.h
> +++ b/libstdc++-v3/include/bits/shared_ptr.h
> @@ -225,7 +225,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> *
> * The last owner will call __d(__p)
> */
> - template<typename _Deleter>
> + template<typename _Deleter, typename = _Constructible<nullptr_t, _Deleter>>
> shared_ptr(nullptr_t __p, _Deleter __d)
> : __shared_ptr<_Tp>(__p, std::move(__d)) { }
>
> Could you add example from LWG4110 (https://cplusplus.github.io/LWG/lwg-active.html#4110),
> I think the resolution requires that we add a dummy parameter (with a default argument).
> which type is well-formed when _ValidDeleter is true.
Done, like so?
Subject: [PATCH] libstdc++: Fix SFINAE with shared_ptr and invalid deleter
[PR126120]
Tested on x86_64-pc-linux-gnu, does this look OK for trunk/16?
Changes in v2:
- Implement LWG 4110 proposed resolution, encoding the _ValidDeleter
constraint as a function parameter instead of a template parameter.
- Remove redundant is_move_constructible constraint in _ValidDeleter
since the derived class's _Constructible constraint already checks
it.
-- >8 --
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.
PR libstdc++/126120
libstdc++-v3/ChangeLog:
* include/bits/shared_ptr.h
(shared_ptr::shared_ptr(nullptr_t, ...)): Add _Constructible
constraint to verify __shared_ptr base initialization is
well-formed. Add _ValidDeleter constraint.
* include/bits/shared_ptr_base.h (_ValidDeleter): New __enable_if_t
constraint.
(__shared_ptr::__shared_ptr(Yp*, _Deleter, ...)): Remove
static_assert checking deleter invocability. Add _ValidDeleter
constraint.
* testsuite/20_util/shared_ptr/cons/126120.cc: New test.
---
libstdc++-v3/include/bits/shared_ptr.h | 15 +++++--
libstdc++-v3/include/bits/shared_ptr_base.h | 21 ++++-----
.../20_util/shared_ptr/cons/126120.cc | 45 +++++++++++++++++++
3 files changed, 65 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 8be9aace1711..6b9acdcffa94 100644
--- a/libstdc++-v3/include/bits/shared_ptr.h
+++ b/libstdc++-v3/include/bits/shared_ptr.h
@@ -212,6 +212,10 @@ _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, breaking some
+ // sensible deleters
+
/**
* @brief Construct a %shared_ptr that owns a null pointer
* and the deleter @a __d.
@@ -225,8 +229,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
*
* The last owner will call __d(__p)
*/
- template<typename _Deleter>
- shared_ptr(nullptr_t __p, _Deleter __d)
+ template<typename _Deleter, typename = _Constructible<nullptr_t, _Deleter>>
+ shared_ptr(nullptr_t __p, _Deleter __d,
+ _ValidDeleter<_Deleter, nullptr_t> = {})
: __shared_ptr<_Tp>(__p, std::move(__d)) { }
/**
@@ -264,8 +269,10 @@ _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)
+ template<typename _Deleter, typename _Alloc,
+ typename = _Constructible<nullptr_t, _Deleter, _Alloc>>
+ shared_ptr(nullptr_t __p, _Deleter __d, _Alloc __a,
+ _ValidDeleter<_Deleter, nullptr_t> = {})
: __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..7c1f965f0bb1 100644
--- a/libstdc++-v3/include/bits/shared_ptr_base.h
+++ b/libstdc++-v3/include/bits/shared_ptr_base.h
@@ -1469,6 +1469,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
{ return static_cast<const __shared_ptr<_Tp, _Lp>*>(this)->get(); }
};
+ // Constraint for well-formedness of deleter expression d(p):
+ template<typename _Deleter, typename _Up>
+ using _ValidDeleter = __enable_if_t<__is_invocable<_Deleter&, _Up&>::value, int>;
+
template<typename _Tp, _Lock_policy _Lp>
class __shared_ptr
: public __shared_ptr_access<_Tp, _Lp>
@@ -1524,24 +1528,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
_M_enable_shared_from_this_with(__p);
}
- template<typename _Yp, typename _Deleter, typename = _SafeConv<_Yp>>
+ 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)
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..0d3f84e2e779
--- /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*) const;
+};
+
+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, "");
+
+struct GenericDeleter {
+ template<typename T> void operator()(T*) const;
+};
+
+void
+test01()
+{
+ // LWG 4110 - shared_ptr(nullptr_t, Deleter) is overconstrained, breaking some sensible deleters
+ shared_ptr p(new int, GenericDeleter{});
+ shared_ptr q(new int, GenericDeleter{}, Alloc{});
+}
--
2.55.0.708.g2c3adbb2c4
>
>
>
> @@ -264,7 +264,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> *
> * The last owner will call __d(__p)
> */
> - template<typename _Deleter, typename _Alloc>
> + template<typename _Deleter, typename _Alloc,
> + typename = _Constructible<nullptr_t, _Deleter, _Alloc>>
> shared_ptr(nullptr_t __p, _Deleter __d, _Alloc __a)
> : __shared_ptr<_Tp>(__p, std::move(__d), std::move(__a)) { }
>
>
>
> diff --git a/libstdc++-v3/include/bits/shared_ptr_base.h b/libstdc++-v3/include/bits/shared_ptr_base.h
> index c8342b7904a5..2eb3436183ee 100644
> --- a/libstdc++-v3/include/bits/shared_ptr_base.h
> +++ b/libstdc++-v3/include/bits/shared_ptr_base.h
> @@ -1469,6 +1469,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> { return static_cast<const __shared_ptr<_Tp, _Lp>*>(this)->get(); }
> };
>
> + // Constraint for well-formedness of deleter expression d(p):
> + template<typename _Deleter, typename _Up>
> + using _ValidDeleter = __enable_if_t<__and_<
> + is_move_constructible<_Deleter>,
> + __is_invocable<_Deleter&, _Up&>
> + >::value>;
> +
> template<typename _Tp, _Lock_policy _Lp>
> class __shared_ptr
> : public __shared_ptr_access<_Tp, _Lp>
> @@ -1524,31 +1531,25 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> _M_enable_shared_from_this_with(__p);
> }
>
> - template<typename _Yp, typename _Deleter, typename = _SafeConv<_Yp>>
> + 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>
> + template<typename _Deleter, typename = _ValidDeleter<_Deleter, nullptr_t>>
> __shared_ptr(nullptr_t __p, _Deleter __d)
> : _M_ptr(0), _M_refcount(__p, std::move(__d))
> { }
>
> - template<typename _Deleter, typename _Alloc>
> + template<typename _Deleter, typename _Alloc,
> + typename = _ValidDeleter<_Deleter, nullptr_t>>
> __shared_ptr(nullptr_t __p, _Deleter __d, _Alloc __a)
> : _M_ptr(0), _M_refcount(__p, std::move(__d), std::move(__a))
> { }
> 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..a5a29597e078
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/shared_ptr/cons/126120.cc
> @@ -0,0 +1,31 @@
> +// 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 Alloc = std::allocator<int>;
> +
> +// 1. Pointer + Deleter
> +static_assert( ! std::is_constructible<std::shared_ptr<int>, int*, NonDeleter>::value, "");
> +static_assert( ! std::is_constructible<std::shared_ptr<int>, int*, NonMoveableDeleter>::value, "");
> +
> +// 2. Pointer + Deleter + Allocator
> +static_assert( ! std::is_constructible<std::shared_ptr<int>, int*, NonDeleter, Alloc>::value, "");
> +static_assert( ! std::is_constructible<std::shared_ptr<int>, int*, NonMoveableDeleter, Alloc>::value, "");
> +
> +// 3. nullptr_t + Deleter
> +static_assert( ! std::is_constructible<std::shared_ptr<int>, std::nullptr_t, NonDeleter>::value, "");
> +static_assert( ! std::is_constructible<std::shared_ptr<int>, std::nullptr_t, NonMoveableDeleter>::value, "");
> +
> +// 4. nullptr_t + Deleter + Allocator
> +static_assert( ! std::is_constructible<std::shared_ptr<int>, std::nullptr_t, NonDeleter, Alloc>::value, "");
> +static_assert( ! std::is_constructible<std::shared_ptr<int>, std::nullptr_t, NonMoveableDeleter, Alloc>::value, "");
> --
> 2.55.0.669.g593c42fe07
>
>
>
More information about the Libstdc++
mailing list