According to cppreference, nullptr is a keyword that:
denotes the pointer literal. It is a prvalue of type std::nullptr_t.
There exist implicit conversions from nullptr to null pointer value of
any pointer type and any pointer to member type. Similar conversions
exist for any null pointer constant, which includes values of type
std::nullptr_t as well as the macro NULL.
So nullptr is a value of a distinct type std::nullptr_t, not int. It implicitly converts to the null pointer value of any pointer type. This magic happens under the hood for you and you don't have to worry about its implementation. NULL, however, is a macro and it is an implementation-defined null pointer constant. It's often defined like this:
#define NULL 0
i.e. an integer.
This is a subtle but important difference, which can avoid ambiguity.
For example:
int i = NULL; //OK
int i = nullptr; //error
int* p = NULL; //OK
int* p = nullptr; //OK
and when you have two function overloads like this:
void func(int x); //1)
void func(int* x); //2)
func(NULL) calls 1) because NULL is an integer.
func(nullptr) calls 2) because nullptr converts implicitly to a pointer of type int*.
Also if you see a statement like this:
auto result = findRecord( /* arguments */ );
if (result == nullptr)
{
...
}
and you can't easily find out what findRecord returns, you can be sure that result must be a pointer type; nullptr makes this more readable.
In a deduced context, things work a little differently. If you have a template function like this:
template<typename T>
void func(T *ptr)
{
...
}
and you try to call it with nullptr:
func(nullptr);
you will get a compiler error because nullptr is of type nullptr_t. You would have to either explicitly cast nullptr to a specific pointer type or provide an overload/specialization for func with nullptr_t.
Advantages of using nulptr:
- avoid ambiguity between function overloads
- enables you to do template specialization
- more secure, intuitive and expressive code, e.g.
if (ptr == nullptr) instead of if (ptr == 0)