Consider the code below:
#include
#include
void f(std::shared_ptr sp) {}
template
std::shared_ptr has a constructor that takes std::nullptr_t, literal 0
is a null pointer constant that is convertiable to std::nullptr_t from the draft C++ standard section 4.10
[conv.ptr] (emphasis mine going forward):
A null pointer constant is an integral constant expression (5.19) prvalue of integer type that evaluates to zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type. Such a conversion is called a null pointer conversion. Two null pointer values of the same type shall compare equal. The conversion of a null pointer constant to a pointer to cv-qualified type is a single conversion, and not the sequence of a pointer conversion followed by a qualification conversion (4.4). A null pointer constant of integral type can be converted to a prvalue of type std::nullptr_t. [ Note: The resulting prvalue is not a null pointer value. —end note ]
in your second case p
is being deduced as type int which although has the value zero is no longer a null pointer constant and so does not fit the same case.
As T.C. points out the wording was changed with DR 903 which requires an integer literal with value zero as opposed to an integral constant expression which evaluates to zero:
A null pointer constant is an integer literal (2.14.2) with value zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type.
The firs call f(0) is compiled as f(nullptr), which is fine for the compiler (but it should not be in my opinion). The second call will create declaration for a function to work on any int, which is illegal.
Funny thing is, that even this code works:
f(3-3);
f(3*0);
Per [conv.ptr]/1 (quoting N4296 here):
A null pointer constant is an integer literal (2.13.2) with value zero or a prvalue of type
std::nullptr_t
. ... A null pointer constant of integral type can be converted to a prvalue of typestd::nullptr_t
.
shared_ptr
has a non-explicit constructor that accepts std::nullptr_t
per [util.smartptr.shared.const]/1:
constexpr shared_ptr(nullptr_t) noexcept : shared_ptr() { }
which constructs an empty, non-owning shared_ptr
.
When you call f(0)
directly, 0
is a null pointer constant that is implicitly converted to shared_ptr<int>
by the above constructor. When you instead call call_f(f, 0)
, the type of the literal 0 is deduced to int
and of course an int
cannot be converted to a shared_ptr<int>
.