How std::reference_wrapper Works in C++

This article provides an overview of how the C++ standard library implements reference wrapper mechanics via std::reference_wrapper. Introduced in C++11, this utility bridges the gap between reference semantics and value semantics, allowing developers to store references in standard containers and pass them through templates that normally decay types to values. Below is an explanation of its internal mechanics, key member functions, and practical applications.

The Problem with Native References

In C++, native references (T&) are not objects. They lack storage identity, cannot be reseated once initialized, and do not possess assignment semantics that overwrite the reference itself (assignment modifies the referent instead). Because of these characteristics, standard containers like std::vector<T&> are ill-formed; standard library containers require elements to be Erasable, CopyAssignable, and MoveAssignable.

Internal Mechanics of std::reference_wrapper

Defined in the <functional> header, std::reference_wrapper<T> is a class template that wraps a reference in a copyable, assignable object. It emulates a reference while exhibiting the mechanics of a first-class object.

Internally, std::reference_wrapper<T> functions essentially as a non-null, copyable pointer:

  1. Internal Storage: The class typically stores an internal pointer to the referenced object: T* ptr;.
  2. Construction: It accepts an lvalue reference of type T& upon construction and stores its address (ptr = std::addressof(val)). Rvalue references are explicitly disallowed; attempting to construct a std::reference_wrapper from a temporary triggers a deleted constructor to prevent dangling references.
  3. Reseatability: Unlike a native reference, copying or assigning a std::reference_wrapper copies the internal pointer, not the underlying object. This allows the wrapper to be reseated to point to a different object during its lifetime.

Type Conversion and Access

std::reference_wrapper<T> provides mechanisms to access the underlying reference transparently:

  • Implicit Conversion Operator: It defines an operator T&() const noexcept that implicitly converts the wrapper back to an lvalue reference whenever an expression expects T&.
  • Explicit Access: The .get() member function returns T& directly, which is useful when template deduction cannot infer the implicit conversion.

Callable Object Invocation

If the underlying type T is a callable entity (such as a function pointer, lambda, or class with operator()), std::reference_wrapper<T> defines an overloaded call operator (operator()). It uses std::invoke to forward any arguments to the stored callable, returning the result. This enables instances of std::reference_wrapper to act as drop-in function objects without copying large or stateful callable types.

Helper Functions: std::ref and std::cref

To avoid manual template instantiation, the standard library provides helper functions:

  • std::ref(val): Deduces the type and returns std::reference_wrapper<T>(val).
  • std::cref(val): Returns std::reference_wrapper<const T>(val).

These helpers are critical when passing arguments to template utilities such as std::bind, std::make_pair, or std::thread, which otherwise decay reference types and make copies of the passed arguments.

Primary Use Cases

  1. Storing References in Containers: A std::vector<std::reference_wrapper<T>> allows the storage of non-owning references inside a standard dynamic array, offering polymorphic or aliased access without heap allocation or pointer syntax.

  2. Passing by Reference to Value-Decaying Templates: Utilities like std::thread copy their arguments by default. Wrapping an argument with std::ref signals to the forwarding machinery that the target function should receive an actual reference rather than a decayed copy.