The answer to “What exactly nullptr is in C++?” would be a piece of cake for experienced C++ eyes & for those who are aware of Modern C++ i.e. keyword. But nullptr
is more than just a keyword in C++ & to explain that, I have written this article. But before jump-into it, we will see issues with NULL
& then we’ll dive into the unsophisticated implementation of nullptr
& some use-cases of nullptr
.
Contents
Why do we need nullptr?
To distinguish between an integer 0(zero) i.e. NULL & actual null of type pointer.
nullptr vs NULL
NULL
is0
(zero) i.e. integer constant zero with C-style typecast tovoid*
, whilenullptr
is prvalue of typenullptr_t
which is integer literal evaluates to zero.- For those of you who believe that
NULL
is same i.e.(void*)0
in C & C++. I would like to clarify that no it’s not:
NULL – cppreference.com (C++)
- C++ requires that macro
NULL
to be defined as an integral constant expression having the value of0
. So unlike in C,NULL
cannot be defined as(void *)0
in the C++ standard library.
Issues with NULL
Implicit conversion
char *str = NULL; // Implicit conversion from void * to char * int i = NULL; // OK, but `i` is not pointer type
Function calling ambiguity
void func(int) {} void func(int*){} void func(bool){} func(NULL); // Which one to call?
Compilation produces the following error:
error: call to 'func' is ambiguous func(NULL); ^~~~ note: candidate function void func(bool){} ^ note: candidate function void func(int*){} ^ note: candidate function void func(int){} ^ 1 error generated. compiler exit status 1
Constructor overload
struct String { String(uint32_t) { /* size of string */ } String(const char*) { /* string */ } }; String s1( NULL ); String s2( 5 );
- In such cases, you need explicit cast (i.e.,
String s((char*)0))
.
Implementation of unsophisticated nullptr
nullptr
is a subtle example of Return Type Resolver idiom to automatically deduce a null pointer of the correct type depending upon the type of the instance it is assigning to.- Consider the following simplest & unsophisticated
nullptr
implementation:
struct nullptr_t { void operator&() const = delete; // Can't take address of nullptr template<class T> inline operator T*() const { return 0; } template<class C, class T> inline operator T C::*() const { return 0; } }; nullptr_t nullptr;
- If the above code seems strange & weird to you(although it should not), then I would suggest you go through my earlier article on advanced C++ concepts. The magic here is just the templatized conversion operator.
- If you are into a more authoritative source, then, here is a concrete implementation of nullptr from LLVM header.
Use-cases of nullptr
struct C { void func(); }; int main(void) { int *ptr = nullptr; // OK void (C::*method_ptr)() = nullptr; // OK nullptr_t n1, n2; n1 = n2; //nullptr_t *null = &n1; // Address can't be taken. }
- As shown in the above example, when
nullptr
is being assigned to an integer pointer, aint
type instantiation of the templatized conversion function is created. And same goes for method pointers too. - This way by leveraging C++ template functionality, we are actually creating the appropriate type of null pointer every time we do, a new type assignment.
- As
nullptr
is an integer literal with value zero, you can not able to use its address which we accomplished by deleting & operator.
Function calling clarity with nullptr
void func(int) { /* ... */} void func(int *) { /* ... */} void func(bool) { /* ... */} func(nullptr);
- Now,
func( int* )
will be called asnullptr
will implicitly be deduced toint*
.
Typecasting on nullptr_t
- A cast of
nullptr_t
to an integral type needs areinterpret_cast
, and has the same semantics as a cast of(void*)0
to an integral type. - Casting
nullptr_t
to an integral type holds true as long as destination type is large enough. Consider this:
// int ptr_not_ok = reinterpret_cast<int>(nullptr); // Not OK long ptr_ok = reinterpret_cast<long long>(nullptr); // OK
- A
reinterpret_cast
cannot convertnullptr_t
to any pointer type. Usestatic_cast
instead.
void func(int*) { /*...*/ } void func(double*) { /*...*/ } func(nullptr); // compilation error, ambiguous call! // func(reinterpret_cast<int*>(nullptr)); // error: invalid cast from type 'std::nullptr_t' to type 'int*' func(static_cast<int*>(nullptr)); // OK
nullptr
is implicitly convertible to any pointer type so explicit conversion withstatic_cast
is only valid.
nullptr_t is comparable
int *ptr = nullptr; if (ptr == 0); // OK if (ptr <= nullptr); // OK int a = 0; if (a == nullptr); // error: invalid operands of types 'int' and 'std::nullptr_t' to binary 'operator=='
From Wikipedia article:
– …null pointer constant: nullptr
. It is of type nullptr_t
, which is implicitly convertible and comparable to any pointer type or pointer-to-member type.
– It is not implicitly convertible or comparable to integral types, except for bool
.
const int a = 0; if (a == nullptr); // OK const int b = 5; if (b == nullptr); // error: invalid operands of types 'const int' and 'std::nullptr_t' to binary 'operator=='
Template-argument is of type std::nullptr_t
template <typename T> void ptr_func(T *t) {} ptr_func(nullptr); // Can not deduce T
- As discussed earlier, Return Type Resolver needs an assignee to deduce the type.
template <typename T> void val_func(T t) {} val_func(nullptr); // deduces T = nullptr_t val_func((int*)nullptr); // deduces T = int*, prefer static_cast though
Conversion to bool from nullptr_t
From cppreference :
– In the context of a direct-initialization, a bool
object may be initialized from a prvalue of type std::nullptr_t
, including nullptr
. The resulting value is false. However, this is not considered to be an implicit conversion.
- The conversion is only allowed for direct-initialization, but not copy-intialization, which including the case for passing an argument to a function by value. e.g.
bool b1 = nullptr; // Not OK bool b2 {nullptr}; // OK void func(bool){} func(nullptr); // Not OK, need to do func(static_cast<bool>(nullptr));
Misc
typeid(nullptr); // OK throw nullptr; // OK char *ptr = expr ? nullptr : nullptr; // OK // char *ptr1 = expr ? 0 : nullptr; // Not OK, types are not compatible static_assert(sizeof(NULL) == sizeof(nullptr_t));
Summary by FAQs
nullptr
introduced?C++11
nullptr
a keyword or an instance of a type std::nullptr_t
? Both true
and false
are keywords & literals, as they have a type ( bool
). nullptr
is a pointer literal of type std::nullptr_t
, & it’s a prvalue (i.e. pure rvalue, you cannot take the address of it using &
). For more.
– No function calling ambiguity between overload sets.
– You can do template specialization with nullptr_t
.
– Code will become more safe, intuitive & expressive. if (ptr == nullptr);
rather than if (ptr == 0);
.
NULL
in C++ equal to nullptr
from C++11?Not at all. The following line does not even compile:cout<<is_same_v<nullptr, NULL><<endl;
nullptr
to bool?Yes. But only if you direct-initialization. i.e. bool is_false{nullptr};
. Else need to use static_cast
.
nullptr
defined?It’s just the templatized conversion operator known as Return Type Resolver.
What exactly nullptr is in C++?
References
You can find similar resources here, here, and in nullptr proposal(N2431); however, this post will walk you through the ins and outs of the spec step-by-step in a more friendly way so that you come away with a full understanding of the concept without any needless confusion
Do you like it☝️? Get such articles directly into the inbox…!?
What exactly nullptr is in C++?