What exactly nullptr is in C++?

Vishal Chovatiya - Apr 13 '20 - - Dev Community

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.

/!\: Originally published @ www.vishalchovatiya.com.

Note: This article is more or less the same thing which you can find here, here & in nullptr proposal(N2431) but in a bit organized & simplified way.

Why do we need nullptr?

To distinguish between an integer 0(zero) i.e. NULL & actual null of type pointer.

nullptr vs NULL

  • NULL is 0(zero) i.e. integer constant zero with C-style typecast to void*, while nullptr is prvalue of type nullptr_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)

NULL - cppreference.com (C++)

  • C++ requires that macro NULL to be defined as an integral constant expression having the value of 0. So unlike in C, NULL cannot be defined as (void *)0 in the C++ standard library.

Issues with NULL

1️⃣ Implicit conversion

char *str = NULL; // Implicit conversion from void * to char *
int i = NULL;     // OK, but `i` is not pointer type
Enter fullscreen mode Exit fullscreen mode

2️⃣ Function calling ambiguity

void func(int) {}
void func(int*){}
void func(bool){}

func(NULL);     // Which one to call?
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

3️⃣ Constructor overload

struct String
{
    String(uint32_t)    {   /* size of string */    }
    String(const char*) {       /* string */        }
};

String s1( NULL );
String s2( 5 ); 
Enter fullscreen mode Exit fullscreen mode
  • 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;
Enter fullscreen mode Exit fullscreen mode

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.
}
Enter fullscreen mode Exit fullscreen mode
  • As shown in the above example, when nullptr is being assigned to an integer pointer, a int type instantiation of the templatized conversion function is created. And same goes for method pointers too.
  • This way by leveraging 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.

1️⃣ Function calling clarity with nullptr

void func(int)   { /* ... */}
void func(int *) { /* ... */}
void func(bool)  { /* ... */}

func(nullptr);
Enter fullscreen mode Exit fullscreen mode
  • Now, func( int* ) will be called as nullptr will implicitly be deduced to int*.

2️⃣ Typecasting on nullptr_t

  • A cast of nullptr_t to an integral type needs a reinterpret_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
Enter fullscreen mode Exit fullscreen mode
  • reinterpret_cast cannot convert nullptr_t to any pointer type. Use static_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
Enter fullscreen mode Exit fullscreen mode
  • nullptr is implicitly convertible to any pointer type so explicit conversion with static_cast is only valid.

3️⃣ 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=='
Enter fullscreen mode Exit fullscreen mode

From Wikipedia: - …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=='
Enter fullscreen mode Exit fullscreen mode

4️⃣ Template-argument is of type std::nullptr_t

template <typename T>
void ptr_func(T *t) {}

ptr_func(nullptr);         // Can not deduce T
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

5️⃣ 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.

bool b1 = nullptr; // Not OK
bool b2 {nullptr}; // OK

void func(bool){}

func(nullptr);     // Not OK, need to do func(static_cast<bool>(nullptr));
Enter fullscreen mode Exit fullscreen mode

6️⃣ 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));
Enter fullscreen mode Exit fullscreen mode

Summary by FAQs

When was nullptr introduced?

C++11

Is 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.

What are the advantages of using nullptr?

- 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);.

Is 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;

Can I convert nullptr to bool?

Yes. But only if you direct-initialization. i.e. bool is_false{nullptr};. Else need to use static_cast.

How is nullptr defined?

It's just the templatized conversion operator known as Return Type Resolver.

Have any suggestions, query or wants to say Hi? Take the Pressure Off, you are just a click away.🖱️

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .