nullptr vs NULL in Modern C++

In modern C++, both nullptr and NULL represent a null pointer value, but they operate under fundamentally different rules. While NULL is a legacy macro inherited from C that evaluates to an integer constant, nullptr is a strongly typed keyword introduced in C++11 specifically to address type ambiguity. Understanding the distinction between the two is essential for writing safe, modern, and predictable C++ code.

The Underlying Definition

NULL is a preprocessor macro defined in headers like <cstddef> or <cstdlib>. In C++, NULL is defined as an integer zero (0 or 0L) because C++ does not allow the implicit conversion of void* to other pointer types without a cast:

#define NULL 0

Because it is an integral literal, NULL does not possess a distinct pointer type.

In contrast, nullptr is a core language keyword of type std::nullptr_t. It is not a macro and cannot be redefined. While nullptr can implicitly convert to any raw pointer type or member pointer type, it cannot implicitly convert to integral types (except bool in conditional evaluations).

Function Overloading Ambiguity

The primary reason nullptr was introduced is function overloading. Because NULL is effectively the integer 0, passing it to overloaded functions leads to unexpected behavior or compilation errors:

void process(int value);
void process(char* ptr);

process(NULL);    // Calls process(int), not process(char*)
process(nullptr); // Correctly calls process(char*)

With process(NULL), the compiler resolves the call to the integer overload because NULL is an integral type. If an overload set contains multiple integer types or ambiguous pointer signatures, NULL can cause a compilation error. Using nullptr eliminates this ambiguity because it binds directly to the pointer overload.

Template Deductions

The integral nature of NULL also causes failures in generic programming. When passing arguments through templates, the compiler deduces the type of the argument based on its actual type:

template <typename F, typename Arg>
void forwarder(F func, Arg arg) {
    func(arg);
}

void targetFunction(int* ptr);

forwarder(targetFunction, NULL);    // Compilation Error
forwarder(targetFunction, nullptr); // Succeeds

In this scenario, Arg is deduced as int when using NULL. The template then attempts to call targetFunction(int), which fails to compile because an int cannot be passed to a function expecting an int*. When using nullptr, Arg is deduced as std::nullptr_t, which cleanly converts to int*.

Summary of Differences

  • Origin: NULL is an inherited C preprocessor macro; nullptr is a C++11 language keyword.
  • Type: NULL is an integer literal (int or long); nullptr has its own type (std::nullptr_t).
  • Implicit Conversions: NULL implicitly converts to numeric types and pointers; nullptr only converts to pointer types (and contextually to bool).
  • Safety: NULL risks incorrect overload selection and template deduction failures; nullptr guarantees clear, type-safe pointer semantics.

In modern C++ development, NULL is considered obsolete. Developers should exclusively use nullptr for all pointer-nullifying operations.