Skip to content
C++
Language
since C++11
Basic

nullptr

The null pointer literal introduced in C++11, typed as std::nullptr_t, that replaces NULL and 0 in pointer contexts with full type safety.

nullptrsince C++11

nullptr is a keyword and prvalue of type std::nullptr_t that represents the universally null pointer constant, converting implicitly to any pointer or pointer-to-member type but never to an integer type.

Overview

Before C++11, null pointers were expressed as the integer literal 0 or the macro NULL, which typically expands to 0 or (void*)0 depending on the implementation. Both are fundamentally integer values that the compiler permits in pointer contexts through implicit conversion. This creates two distinct problems: overload resolution becomes ambiguous between pointer and integer overloads, and generic code loses the null-pointer meaning when the integer constant is deduced as int.

C++11 introduced nullptr as a dedicated null pointer literal with its own type: std::nullptr_t, declared in <cstddef>. This type is distinct from all pointer types, yet it converts implicitly to any pointer type and any pointer-to-member type. Critically, it does not convert to int or any other arithmetic type. That single distinction eliminates the class of overload ambiguity and template deduction failures that plagued pre-C++11 null pointer handling.

nullptr is a keyword, not a macro. It is a prvalue (pure rvalue) in the sense of C++11 value categories β€” it has no identity, no address, and cannot appear on the left-hand side of an assignment. Every expression of type std::nullptr_t is a null pointer constant, meaning it converts to null of any pointer type.

std::nullptr_t is a complete, regular type. Variables may be declared of type std::nullptr_t, it may appear as a template argument, it participates in overload resolution as its own distinct type, and since C++11 it may appear in constant expressions evaluated at compile time.

Syntax

cpp
nullptr

The keyword itself requires no header. To name the type std::nullptr_t explicitly in code, include <cstddef>.

cpp
#include <cstddef>

std::nullptr_t np = nullptr;    // C++11: variable of type std::nullptr_t
int*          ip = nullptr;    // C++11: null int*
void        (*fn)() = nullptr; // C++11: null function pointer
int     Foo::* mp = nullptr;   // C++11: null pointer-to-data-member

Examples

Overload Resolution β€” The Core Motivation

cpp
#include <cstddef>
#include <iostream>

void process(int n)  { std::cout << "integer: " << n << '\n'; }
void process(int* p) { std::cout << "pointer: " << (p ? "non-null" : "null") << '\n'; }

int main()
{
    process(0);        // calls process(int)  β€” may surprise callers
    process(NULL);     // calls process(int)  β€” NULL is an integer constant
    process(nullptr);  // calls process(int*) β€” unambiguous, C++11
}

With 0 or NULL, the compiler resolves to process(int) because those are integer constants. nullptr has type std::nullptr_t, which does not convert to int, leaving only the pointer overload viable.

Templates and Type Deduction

nullptr retains its identity through template instantiation; 0 and NULL do not:

cpp
#include <cstddef>
#include <iostream>

template<class T>
constexpr T forwarded(const T& v) { return v; } // C++11

void sink(int*) { std::cout << "pointer\n"; }

int main()
{
    sink(forwarded(nullptr));  // OK: T = std::nullptr_t, converts to int*
 // sink(forwarded(0));        // error: T = int, not convertible to int*
 // sink(forwarded(NULL));     // error: same β€” NULL deduces as int
}

This distinction is critical in middleware and generic wrappers that forward null sentinels through multiple levels of deduction. nullptr is the only null constant that survives deduction intact.

Overloading on std::nullptr_t

When a type should accept only nullptr and reject arbitrary integers, overload its constructor or assignment operator on std::nullptr_t directly:

cpp
#include <cstddef>

class Handle {
public:
    explicit Handle(void* raw) noexcept : ptr_(raw) {}
    Handle(std::nullptr_t)    noexcept : ptr_(nullptr) {} // C++11: reset overload

    Handle& operator=(std::nullptr_t) noexcept {          // C++11
        ptr_ = nullptr;
        return *this;
    }

    explicit operator bool() const noexcept { return ptr_ != nullptr; }

private:
    void* ptr_;
};

Handle h{nullptr};  // uses Handle(std::nullptr_t)
h = nullptr;        // uses operator=(std::nullptr_t)
// Handle bad{0};   // error: no conversion from int to Handle

This pattern is used throughout the standard library in std::unique_ptr, std::shared_ptr, and std::function to provide clean reset semantics without accidentally accepting 0.

auto and nullptr

auto deduces std::nullptr_t, not a pointer type:

cpp
auto p = nullptr;   // p is std::nullptr_t β€” NOT void*, NOT int*
// *p;              // error: cannot dereference std::nullptr_t

int* ip = nullptr;  // explicit type: fine
auto q = static_cast<int*>(nullptr); // C++11: force pointer type via cast

When you need a typed null pointer via auto, either declare the type explicitly or cast. This deduction behaviour can surface as an error far from the declaration site, so explicit pointer types are preferable in these situations.

Equality and Comparison

nullptr compares equal to any null pointer value of any pointer type:

cpp
#include <cstddef>

int* p  = nullptr;
void* q = nullptr;

bool a = (p == nullptr);   // true
bool b = (q == nullptr);   // true
// bool c = (p == q);      // error: cannot compare int* and void* directly

std::nullptr_t n1 = nullptr;
std::nullptr_t n2 = nullptr;
bool d = (n1 == n2);       // true: all std::nullptr_t values compare equal
bool e = (n1 < n2);        // false: relational operators are also defined (C++11)

Best Practices

Always use nullptr instead of NULL or 0 in pointer contexts. The type distinction is enforced by the compiler and prevents silent resolution to integer overloads.

Overload on std::nullptr_t when a class has a distinct "reset to null" operation. This makes the API explicit, prevents accidental construction from the integer 0, and documents intent at the call site.

Do not use auto to capture nullptr when you need a pointer. Declare the concrete pointer type or assign to a typed pointer first. The std::nullptr_t type has no dereference, arithmetic, or indexing operations.

In generic code, always pass nullptr rather than 0 through deduction contexts. A 0 that enters template<class T>(T v) becomes int and loses all pointer-convertibility. nullptr never does.

Prefer std::nullptr_t parameters over pointer parameters for factory/reset functions when the semantic meaning is strictly "set to null," not "accept any pointer." It prevents callers from accidentally passing live pointers.

Common Pitfalls

sizeof(std::nullptr_t) Is Not Guaranteed to Match sizeof(void*)

std::nullptr_t is an independent type. Its size is implementation-defined and need not match any pointer size:

cpp
// May or may not hold on a given platform:
static_assert(sizeof(std::nullptr_t) == sizeof(void*));  // NOT guaranteed

Do not use std::nullptr_t as a proxy for pointer size in low-level code.

Boolean Conversion Is Always false

A value of type std::nullptr_t is always null by definition. Boolean conversion always yields false, and compilers typically warn on if (nullptr):

cpp
std::nullptr_t n = nullptr;
if (n) { /* unreachable */ }  // compiler warning; condition is always false

The warning is correct. There is no way to construct a non-null std::nullptr_t.

nullptr converts to pointer-to-member types, which is syntactically valid but undefined behaviour if dereferenced:

cpp
struct Foo { int x; };
int Foo::* mp = nullptr;  // legal: null pointer-to-data-member

Foo obj;
// obj.*mp;               // undefined behaviour: dereferencing null pointer-to-member

The type system does not prevent this. The hazard is identical to dereferencing a null object pointer but occurs in a context developers may not expect to be dangerous.

nullptr Cannot Be Passed to a Variadic Function Correctly

Because variadic functions (printf, va_list-based APIs) do not have typed parameters, nullptr is passed as std::nullptr_t, not as void*. On platforms where the two have different representations or sizes this causes undefined behaviour:

cpp
printf("%p\n", nullptr);              // undefined behaviour
printf("%p\n", (void*)nullptr);       // correct: explicit cast to void*

Always cast nullptr to the expected pointer type before passing it to C-style variadic interfaces.

See Also

  • std::nullptr_t β€” the type of nullptr, declared in <cstddef> (C++11); usable as a template parameter and function parameter type
  • NULL β€” the legacy null pointer macro from <cstddef> and related headers; prefer nullptr in all new C++11 and later code
  • auto β€” type deduction; auto x = nullptr deduces std::nullptr_t, not any pointer type