On Tue, 25 Aug 2026 at 14:54, Patrick Palka <[email protected]> wrote:
> 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
> (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_type): New.
> (_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 | 23 +++++-----
> .../20_util/shared_ptr/cons/126120.cc | 45 +++++++++++++++++++
> 3 files changed, 67 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..e4dc40157c52 100644
> --- a/libstdc++-v3/include/bits/shared_ptr_base.h
> +++ b/libstdc++-v3/include/bits/shared_ptr_base.h
> @@ -1469,6 +1469,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> { return static_cast<const __shared_ptr<_Tp, _Lp>*>(this)->get(); }
> };
>
> + // Constraint for well-formedness of deleter expression d(p):
> + struct _ValidDeleter_type { };
> + template<typename _Deleter, typename _Up>
> + using _ValidDeleter = __enable_if_t<__is_invocable<_Deleter&,
> _Up&>::value,
> + _ValidDeleter_type>;
This could be local to __shared_ptr, and private, so it isn't at
namespace scope.
> +
> template<typename _Tp, _Lock_policy _Lp>
> class __shared_ptr
> : public __shared_ptr_access<_Tp, _Lp>
> @@ -1524,24 +1530,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");
I wonder if we should move this static_assert into the reset(Y*, D)
member. Previously passing and invalid deleter to reset would trigger
this assertion, with a nice message. Now it will fail overload
resolution for the constructor.
We could put the static_assert in reset(Y*, D) to restore the nice message.
> - _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..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
> --
> 2.55.0.708.g2c3adbb2c4
>
> >
> >
> > 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{});
> >
> > I do not think that above example reproduces problem from the issue,
> > checking is invocable
> > <GenericDeleter, nullptr> is simply true. The lambda with auto was
> > important, as checking is
> > invocable triggered return type deduction, which instantiated delete with
> > nullptr, that was ill-formed.
> > +}
> > --
> > 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
> > >
> > >
> > >
> >
> >
> >