On Tue, Aug 25, 2026 at 3:27 PM Patrick Palka <[email protected]> wrote:
> On Tue, 25 Aug 2026, Patrick Palka wrote: > > > On Tue, 25 Aug 2026, Tomasz Kaminski wrote: > > > > > > > > > > > On Tue, Aug 25, 2026 at 6:11 AM Patrick Palka <[email protected]> > 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; > > +}; > > Whoops, in trying to make the LWG 4110 testcase C++11 compatible I > made it no longer demonstrate the issue at all. The call operator > must have a deduced return type in order for the testcase to break > without the proposed resolution. > You can use something like: template<typename T> struct non_nullptr { static_assert(!is_same_v<T, nullptr>); using type = void; }; and then return from operator() of deleted: typename non_nullptr<T>::type > > > + > > +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 > > > > > > > > > >
