This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
shared_ptr(nullptr_t, Deleter) is overconstrained, breaking some sensible deletersSection: 20.3.2.2.2 [util.smartptr.shared.const] Status: New Submitter: Louis Dionne Opened: 2024-06-11 Last modified: 2025-10-22
Priority: 3
View other active issues in [util.smartptr.shared.const].
View all other issues in [util.smartptr.shared.const].
View all issues with New status.
Discussion:
The following code doesn't compile on conforming implementations:
#include <memory>
void f() {
std::shared_ptr<int>(new int, [](auto pointer) { delete pointer; });
}
(Godbolt)
This is caused by the constraint on shared_ptr(nullptr_t p, D d);
being that d(p) is valid (20.3.2.2.2 [util.smartptr.shared.const] p9),
which leads to a hard error inside the lambda since it is called with a
nullptr_t. This seems unintended.
See LLVM issue 93071 comment for additional context.
[2025-10-22; Reflector poll.]
Set priority to 3 after reflector poll.
"I don't agree with the proposed resolution. As a general principle,
shared_ptr<T>(p, d) always calls d(p) and never
d(static_cast<T*>(p)).
If we really want to make this work, which is not unreasonable, even though
the fix on the user side is trivial, we should make the nullptr_t constructor
templated on same_as<nullptr_t>
(or convertible_to<nullptr_t>?)."
"That would break passing NULL, only nullptr would work.
It can be made to work by checking that d(p) is well-formed in a
function parameter with a default argument,
instead of as a template parameter:"
struct shared_ptr {
template<class Y, class D>
shared_ptr(Y* p, D d, std::void_t<decltype(d(p))>* = nullptr) {}
template<class D>
shared_ptr(std::nullptr_t p, D d, std::void_t<decltype(d(p))>* = nullptr) {}
};
shared_ptr s(new int, [](auto p) {delete p;});
"Ugh. We don't have to use it everywhere, only these two specific constructors."
Proposed resolution:
This wording is relative to N4981.
shared_ptr(nullptr_t p, D d); checks whether
d(static_cast<T*>(nullptr))
is well-formed. This requires expressing the the constraints for
the Y* constructors and the nullptr_t constructors separately,
which is mostly editorial:
template<class Y, class D> shared_ptr(Y* p, D d); template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);template<class D> shared_ptr(nullptr_t p, D d); template<class D, class A> shared_ptr(nullptr_t p, D d, A a);-9- Constraints:
is_move_constructible_v<D>istrue, andd(p)is a well-formed expression.For the first two overloads:
- (9.1) If
Tis an array type, then eitherTisU[N]andY(*)[N]is convertible toT*, orTisU[]andY(*)[]is convertible toT*.- (9.2) If
Tis not an array type, thenY*is convertible toT*.template<class D> shared_ptr(nullptr_t p, D d); template<class D, class A> shared_ptr(nullptr_t p, D d, A a);-?- Constraints:
is_move_constructible_v<D>istrue, andd(static_cast<T*>(p))is a well-formed expression.