Skip to content

C++ language usability enhancements — nullptr and constexpr

A compact guide covering two small but important features that improve code clarity and safety: nullptr (C++11) and constexpr (C++11 and later).


1. nullptr — the modern null pointer literal

nullptr is a keyword (since C++11) that denotes a null pointer literal of type std::nullptr_t. It converts implicitly to any pointer type and to pointer-to-member types, but it does not convert to integers. This makes code using nullptr safer and clearer than older idioms that used NULL or 0.

Quick summary

  • Introduced in C++11
  • Type: std::nullptr_t
  • Converts to: raw pointer types and pointer-to-member types
  • Does not convert to: ordinary integer types

Why prefer nullptr?

  • NULL is typically a macro expanding to 0 (an integer). That can cause surprising overload resolution results.
  • nullptr has its own dedicated type, so it won't accidentally match integer overloads.
  • Improves code clarity: nullptr unambiguously means "pointer with no target."

Small contract

  • Inputs: none (it's a literal)
  • Outputs: a value of type std::nullptr_t that converts to pointer types
  • Error modes: none
  • Success: useable in comparisons, assignments, overload resolution and template deduction where a null pointer is required

Examples

Basic overload resolution:

#include <iostream>
#include <type_traits>

void f(int) { std::cout << "f(int)\n"; }
void f(char*) { std::cout << "f(char*)\n"; }

int main() {
    // Ambiguous or surprising with 0 or NULL:
    // f(0);    // calls f(int)
    // f(NULL); // may call f(int) depending on how NULL is defined

    f(nullptr); // unambiguously calls f(char*)

    std::nullptr_t n = nullptr;
    static_assert(std::is_same<decltype(n), std::nullptr_t>::value, "type is nullptr_t");
}

Output:

f(char*)

Template deduction example:

template<typename T>
void h(T) { /* ... */ }

int main() {
    h(nullptr); // T deduced as std::nullptr_t
}

Pointer-to-member and smart pointers:

struct S { int x; };

int main() {
    int S::*pm = nullptr;         // pointer-to-member initialized to null

    std::unique_ptr<int> up = nullptr;
    std::shared_ptr<int> sp(nullptr);
    up.reset(); // sets to nullptr
}

Interactions with C and varargs

Be careful passing nullptr to C APIs that use varargs or expect integer types. nullptr is not an integer, so pass explicit integral values when the C API expects them.

Bad example:

// printf expects an int for %d — don't pass nullptr
// printf("%d\n", nullptr); // wrong: type mismatch

printf("%d\n", 0); // correct

If you must bridge to a C API that expects a pointer-sized integer, cast explicitly and document why:

// Only when the API really expects an integer representation of a pointer
void c_api(void* p);
c_api(static_cast<void*>(nullptr));

Pitfalls and notes

  • Avoid comparing nullptr to integers in normal code. Although nullptr == 0 is allowed (and yields true), comparisons with integers defeat the purpose of using a pointer literal with a dedicated type.
  • sizeof(nullptr) gives the size of std::nullptr_t, which is implementation-defined. Prefer relying on types rather than sizes.
  • In template code, if you want a pointer type deduced, use a signature that requires a pointer (e.g., T*) rather than relying on nullptr to force a pointer deduction.

Best practices

  • Use nullptr everywhere you previously used NULL or 0 to mean a null pointer.
  • Prefer function signatures that make pointer intent explicit (e.g., use T* when you really want a pointer), which plays well with nullptr and template deduction.
  • When interacting with C or varargs, ensure you pass values of the expected type; use explicit casts if necessary and comment why.

Quick reference

  • Keyword: nullptr
  • Type: std::nullptr_t
  • Introduced: C++11
  • Use for: null pointer literals (raw pointers, pointer-to-member, smart pointers initialization)

2. constexpr — compile-time computation and constants

constexpr (since C++11, with many relaxations in later standards) marks functions and variables that can be evaluated at compile time. Well-placed constexpr improves performance, gives stronger guarantees, and documents intent.

Quick summary

  • Introduced: C++11 (improved in C++14, C++17, C++20)
  • Use for: compile-time constant expressions, functions usable in constant expressions
  • Variants: constexpr functions/variables, consteval (C++20 immediate function), constinit (C++20 static initialization)

Example — constexpr function (factorial)

#include <array>
#include <iostream>

constexpr int factorial(int n) {
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

int main() {
    constexpr int f5 = factorial(5); // computed at compile time
    std::cout << "5! = " << f5 << '\n';

    // Use in contexts requiring compile-time constants
    std::array<int, factorial(4)> arr{}; // size is 24
    (void)arr;
}

This code compiles with C++11 and later; C++14 relaxed constexpr rules make writing non-trivial constexpr functions easier.

consteval and constinit (C++20)

consteval int make_id() { return 42; } // must be evaluated at compile time

constinit int global_counter = 0; // ensures static initialization, not zero-initialized at runtime

int main() {
    constexpr int id = make_id();
    (void)id;
}

Use consteval when a function must produce a compile-time value. Use constinit to ensure static variables are initialized at compile time where required.

Pitfalls and notes

  • Overusing constexpr can force more work onto the compiler; apply it where compile-time evaluation is beneficial.
  • Not all code can be constexpr — I/O, most dynamic allocations, and certain system interactions must remain runtime-only.
  • As the language evolves, more constructs become constexpr-friendly (C++14/17/20), so prefer writing constexpr-capable code when appropriate.

Summary

  • nullptr improves pointer semantics and overload clarity; use it in C++11+ code.
  • constexpr moves computation to compile time, documents intent, and enables stronger guarantees; use it where compile-time evaluation is helpful, and prefer the newer features (consteval, constinit) when applicable.