How gsl::not_null Enforces Non-Null Pointers in C++
This article explains how the C++ Core Guidelines Support Library
utility gsl::not_null eliminates null pointer dereferencing
bugs. It covers the dual mechanism of static compile-time
prevention—such as deleted default and nullptr
constructors—and runtime contract verification during dynamic
initialization, providing zero-overhead, self-documenting pointer
safety.
What is gsl::not_null?
gsl::not_null<T> is a zero-overhead template
wrapper designed for pointer types, including raw pointers
(T*), standard smart pointers
(std::unique_ptr, std::shared_ptr), and
user-defined pointer-like objects. Its sole purpose is to extend the C++
type system by establishing an invariant: the encapsulated pointer can
never hold a null value (nullptr).
Compile-Time Enforcement
gsl::not_null leverages C++ type constraints and
constructor overloads to eliminate entire categories of null pointer
operations during compilation:
Deleted
nullptr_tConstructor: The wrapper explicitly deletes the constructor takingstd::nullptr_t:not_null(std::nullptr_t) = delete; not_null& operator=(std::nullptr_t) = delete;Attempting to initialize or assign a
gsl::not_nullobject directly withnullptrcauses an immediate compilation failure.No Default Constructor: Standard pointers default-initialize to an indeterminate state or
nullptr.gsl::not_nulldeletes or omits the default constructor, requiring an explicit valid pointer object at the point of declaration.Explicit Interface Contracts: Functions defined with
gsl::not_null<T*>parameters cannot accidentally accept raw pointers without an explicit conversion. This forces the caller to either guarantee the pointer is non-null or handle the validation at the boundary.
Dynamic Construction and Runtime Enforcement
When a pointer's value is determined dynamically at runtime—such as
the result of a system call, memory allocation, or external
function—compile-time checks alone cannot guarantee safety.
gsl::not_null enforces its invariant through runtime
contract checks during construction.
Upon receiving a raw or smart pointer:
template <typename U>
constexpr explicit not_null(U&& u) : ptr_(std::forward<U>(u)) {
Expects(ptr_ != nullptr);
}The constructor validates the incoming pointer using the GSL contract
macro Expects(). If the provided argument evaluates to
nullptr at runtime:
- The program triggers a contract violation.
- By default, this immediately terminates execution (via
std::terminate), avoiding undefined behavior further down the call stack. - In customized environments, it can be configured to throw an exception or invoke a designated error handler.
Once construction succeeds, the invariant is locked. Any subsequent assignment to the wrapper also passes through these validation checks.
Access and Runtime Efficiency
Because gsl::not_null guarantees its invariant at
construction, member access via operator* and
operator-> executes without repetitive runtime null
checks:
void process(gsl::not_null<int*> data) {
// No "if (data == nullptr)" check needed; guaranteed valid
*data = 42;
}Modern compilers inline these operators completely. In optimized
builds (-O2 or -O3),
gsl::not_null<T*> generates the exact same assembly
as a bare raw pointer, providing type-level safety with zero runtime
performance cost after initialization.