[PATCH] libstdc++: Fix SFINAE with shared_ptr and invalid deleter [PR126120]

Tomasz Kaminski tkaminsk@redhat.com
Tue Aug 25 06:24:28 GMT 2026


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.



>
> @@ -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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260825/1dd093ed/attachment-0001.htm>


More information about the Libstdc++ mailing list