nullptr — Type-Safe Null Pointer Literal
Overview
Before C++11, there were two ways to represent a null pointer: 0 and NULL. Both introduce ambiguity — the compiler cannot syntactically distinguish "integer zero" from "null pointer." C++11 introduced the nullptr keyword, of type std::nullptr_t, completely eliminating the semantic confusion between null pointers and integers.
Why nullptr Is Needed
In traditional C++, NULL is typically defined as 0, which serves both integer and pointer roles:
void f(int);
void f(char*);
f(0); // calls f(int) — 0 is an integer literal
f(NULL); // calls f(int) on most compilers — NULL expands to 0The programmer intends to call f(char*), but NULL expands to 0, so the compiler matches f(int). The problem is worse in template scenarios:
template <typename T>
void process(T value) { /* ... */ }
process(0); // T = int — probably not what you want
process(NULL); // T = int — same problem
process(nullptr); // T = std::nullptr_t — correctOnce T is deduced as int, subsequent pointer-based operations will produce compilation errors or undefined behavior, with extremely cryptic error messages.
std::nullptr_t Type
The type of nullptr is std::nullptr_t (defined in <cstddef>), with these key properties:
- Implicitly convertible to any pointer type (including function pointers and member pointers)
- Not implicitly convertible to integer types
- Not usable in arithmetic operations
#include <cstddef>
#include <type_traits>
static_assert(sizeof(nullptr) == sizeof(void*), "pointer-sized");
static_assert(std::is_same<decltype(nullptr), std::nullptr_t>::value, "");
int* p1 = nullptr; // OK
void (*fp)() = nullptr; // OK — function pointer
int S::* mp = nullptr; // OK — member pointer
// int x = nullptr; // error: conversion from nullptr_t to non-scalar typeBehavior in Overload Resolution
void handle(int value) { /* int overload */ }
void handle(int* ptr) { /* pointer overload */ }
handle(0); // calls handle(int)
handle(NULL); // calls handle(int) on most platforms
handle(nullptr); // calls handle(int*) — unambiguouslynullptr clearly expresses "null pointer" semantics; the compiler will not mistake it for an integer.
Interaction with Templates
Smart Pointer Scenario
template <typename T>
class SmartPtr {
public:
SmartPtr(T* p) : ptr_(p) {}
bool operator==(std::nullptr_t) const { return ptr_ == nullptr; }
private:
T* ptr_;
};
SmartPtr<int> sp(nullptr);
if (sp == nullptr) { /* compiles cleanly */ }Cannot Deduce Pointed-to Type
nullptr itself has no pointed-to type and cannot be used directly in scenarios requiring deduction of T*:
template <typename T>
void takes_ptr(T* p) { /* ... */ }
takes_ptr(nullptr); // error: cannot deduce T from nullptr_t
takes_ptr(static_cast<int*>(nullptr)); // OK — explicitCommon Pitfalls
Pitfall 1: Ambiguity with bool Overloads
void f(int*);
void f(bool);
f(nullptr); // C++11/14: ambiguous — nullptr converts to both int* and boolWhen this occurs, use an explicit cast or redesign the interface.
Pitfall 2: Using nullptr in Boolean Contexts
int* p = get_pointer();
if (p) { /* OK — idiomatic */ }
if (p != nullptr) { /* OK — more explicit, preferred in modern C++ */ }Both are correct, but != nullptr makes intent clearer in code review.
Pitfall 3: C-style API Boundaries
extern "C" void c_function(void* p);
c_function(nullptr); // OK — nullptr converts to void*On all major ABIs, nullptr's representation is consistent with C's null pointer, but the standard does not guarantee this.
Migration Guide from NULL / 0
Step 1: Globally replace NULL — Replace all NULL instances representing null pointers with nullptr (keep those in macro definitions).
Step 2: Replace literal 0 — 0 is sometimes genuinely integer zero; requires manual judgment:
int* p = 0; // → int* p = nullptr;
if (p == 0) { } // → if (p == nullptr) { }
return 0; // in pointer-returning ctx → return nullptr;Step 3: Compiler warning-assisted migration:
clang++ -Wzero-as-null-pointer-constant -std=c++11 source.cpp
g++ -Wzero-as-null-pointer-constant -std=c++11 source.cppThis warning reports at every location where 0/NULL is used as a null pointer, making it a powerful migration tool.
Best Practices
- Always use
nullptrto represent null pointers, never0orNULL. - In template code,
nullptris the only correct null pointer representation — it does not pollute type deduction. - For function parameters with "optional pointer" semantics, use
nullptrrather than a default argument of= 0. - Enable the
-Wzero-as-null-pointer-constantcompiler warning; enforce it in CI. - Do not mix
nullptrwithbooloverloads — if an interface accepts both pointers and booleans, redesign the API. NULLis still acceptable in C-style APIs, but the C++ side should immediately convert tonullptr.