Skip to content
C++
Library
since C++11
Basic

Reference Wrapper

std::reference_wrapper, std::ref and std::cref: copyable, assignable reference semantics for containers, bind, threads and generic callables.

std::reference_wrappersince C++11

A copyable, assignable class template in <functional> that stores a reference to an object or function of type T and converts implicitly back to T&, so references can pass through code that would otherwise copy its arguments.

Overview

A native reference cannot be rebound, cannot be stored in a standard container, and decays to a copy when passed to templates that take parameters by value. std::reference_wrapper<T> (C++11) fixes this. Internally it holds a pointer, but it presents reference-like semantics: copying it copies the binding (not the referent), and assigning it rebinds it.

Key properties:

  • Implicit conversion to T& (operator T&()), plus explicit access via get().
  • Rebindable: a = b on two wrappers makes a refer to b's target. It never assigns through to the referent.
  • Trivially copyable (guaranteed since C++20 for the copy operations being trivial in practice; treat it as cheap, pointer-sized).
  • Callable: if T is callable, operator() forwards to it, with the return type computed via std::invoke_result (C++17 semantics; in C++11/14 via result_of).
  • Factories: std::ref(x) and std::cref(x) (C++11) deduce T and const T. Passing an existing reference_wrapper to ref returns it rather than nesting wrappers.
  • Binding to rvalues is deleted: std::ref(std::string{"tmp"}) does not compile. cref of an rvalue is also deleted (const T&& overloads are deleted since C++11).

Changes by standard:

  • C++14: ref/cref gained the constexpr specifier on their overloads... in practice, they are constexpr from C++20 (reference_wrapper members and ref/cref are constexpr since C++20).
  • C++20: all members and std::ref/std::cref are constexpr.
  • C++26: adds a reference_wrapper comparison (operator==, and with it <=> for the wrapped type) when the underlying type supports it; before C++26 wrappers are not comparable unless converted through get() or implicit conversion.
  • C++26: also adds the ability to construct wrappers from a T& without ambiguity for types that themselves are convertible to reference_wrapper (a constraint tweak).

Syntax

cpp
#include <functional>

int x = 0;
std::reference_wrapper<int> r = std::ref(x);   // C++11
std::reference_wrapper<const int> c = std::cref(x); // C++11

int& a = r;        // implicit conversion
int& b = r.get();  // explicit access
r.get() = 5;       // modifies x

// Callable wrapper (C++11)
void hello(int);
std::reference_wrapper<void(int)> fw = std::ref(hello);
fw(42);

// CTAD (C++17)
std::reference_wrapper rr = std::ref(x);   // deduces reference_wrapper<int>

Member type (the wrapped type) is available as reference_wrapper<T>::type (C++11), with result_type and argument_type only for function types (deprecated in C++17, removed in C++20).

Examples

Containers of references (C++11)

cpp
#include <functional>
#include <vector>
#include <algorithm>
#include <iostream>

struct Widget { int id; };

int main() {
    Widget a{3}, b{1}, c{2};
    std::vector<std::reference_wrapper<Widget>> view{a, b, c};   // C++11

    // Sort the view without moving the widgets themselves
    std::sort(view.begin(), view.end(),
              [](const Widget& l, const Widget& r) { return l.id < r.id; });

    view[0].get().id = 100;                // modifies the original (b)
    std::cout << b.id << '\n';             // 100
}

The lambda parameters take const Widget&, so the implicit conversion applies. Note that view[0].id would not compile: member access does not pass through the wrapper, which is why .get() is needed.

Threads and std::bind (C++11)

std::thread, std::async and std::bind decay-copy their arguments. Without ref, a function taking T& will not compile (thread) or will silently mutate a copy (bind).

cpp
#include <functional>
#include <thread>
#include <vector>

void fill(std::vector<int>& out, int value) { out.push_back(value); }

int main() {
    std::vector<int> data;

    std::thread t(fill, std::ref(data), 7);   // C++11: reference passed through
    t.join();                                  // data == {7}

    auto bound = std::bind(fill, std::ref(data), std::placeholders::_1); // C++11
    bound(8);                                  // data == {7, 8}
}

Lifetime is your responsibility: the referent must outlive the thread or bound object. Concurrent access still requires synchronization.

Avoiding copies of stateful callables (C++11)

cpp
#include <algorithm>
#include <functional>
#include <vector>

struct Counter {
    int calls = 0;
    bool operator()(int v) { ++calls; return v % 2 == 0; }
};

int main() {
    std::vector<int> v{1, 2, 3, 4, 5, 6};
    Counter pred;
    // Most algorithms may copy the predicate; wrapping preserves the state
    auto n = std::count_if(v.begin(), v.end(), std::ref(pred));
    // n == 3, pred.calls == 6
}

Unlike std::function, which stores a copy of the callable, a wrapper stores a reference. This is also the standard way to keep a non-copyable or expensive-to-copy functor out of a std::function: std::function<bool(int)> f = std::ref(pred); (C++11) performs no allocation for the wrapper.

Tuples, pairs and optional

cpp
#include <functional>
#include <optional>
#include <tuple>
#include <string>

int main() {
    int i = 0; double d = 0; std::string s;

    // Unpack a tuple into existing variables (C++11)
    std::make_tuple(std::ref(i), std::ref(d), std::ref(s)) =
        std::make_tuple(1, 2.5, std::string("hi"));
    // Prefer std::tie(i, d, s) for this exact purpose

    auto p = std::make_pair(std::ref(i), std::ref(i)); // pair<int&, int&>
    ++p.first; ++p.second;                             // i == 3

    // std::optional<T&> is ill-formed (C++17); use a wrapper
    std::optional<std::reference_wrapper<std::string>> maybe = std::ref(s); // C++17
    if (maybe) maybe->get() += "!";
}

std::invoke (C++17) and std::mem_fn/std::bind unwrap a reference_wrapper when it is supplied as the object for a pointer-to-member call, so std::invoke(&Widget::id, std::ref(w)) works.

Seeding and sharing random engines (C++11)

cpp
#include <algorithm>
#include <functional>
#include <random>
#include <array>

int main() {
    std::random_device rd;
    std::array<unsigned, 8> seed_data;
    std::generate(seed_data.begin(), seed_data.end(), std::ref(rd)); // no copy of rd
}

Algorithms such as std::generate and std::shuffle take engines or generators by value or forwarding reference; wrapping an engine ensures its state advances in the caller.

Best Practices

  • Use std::ref/std::cref at API boundaries that copy (threads, bind, async, make_tuple, make_pair), not inside general code where a plain reference works.
  • Prefer a lambda capture by reference ([&x], C++11) over std::bind(f, std::ref(x), _1); lambdas are clearer and often better optimized. Lambdas are preferred over bind since C++14.
  • Use std::cref when the callee reads only, so a non-const object can bind to a const T& parameter without accidental mutation.
  • Use reference_wrapper as the element type for "views" into another container when you need to reorder or filter without copying elements. For a non-owning, nullable, rebindable handle, a raw pointer is often equally good.
  • Call .get() explicitly when deducing with auto or calling templates; implicit conversion does not happen during template deduction.

Common Pitfalls

  • Dangling references: the wrapper does not extend lifetime. A wrapper in a detached thread or stored std::function outliving its target is undefined behavior.
  • Rvalues are rejected: std::ref(make_string()) is a deleted overload by design, preventing a guaranteed dangling wrapper.
  • No member access: w.member and w->member do not work on reference_wrapper<T>. Use w.get().member.
  • Deduction with auto: auto x = std::ref(i); makes x a reference_wrapper<int>, not int&. Arithmetic still works through conversion (x + 1), but x = 5 does not assign to i; it fails to compile for int, and for class types it may rebind rather than assign.
  • Assignment semantics differ from references: w1 = w2 rebinds. To assign through, write w1.get() = w2.get();.
  • Overload resolution surprises: std::cout << std::ref(i) works via conversion for fundamental types, but templates such as std::format (C++20) or user-defined generic printers that take T by deduction will see reference_wrapper<int> and may fail to find a formatter.
  • Comparisons: before C++26, reference_wrapper<T> has no operator==; comparisons work only through implicit conversion when T has a non-template comparison, and fail in containers needing deduced operators (for example std::find on a vector of wrappers with a plain T value usually works through conversion, but std::set of wrappers needs a custom comparator).
  • Thread safety: wrapping a shared object into several threads shares it; you must provide a mutex or use std::atomic for the data.
  • std::unwrap_reference / std::unwrap_ref_decay (C++20): generic code that wants to treat wrappers as references (as make_pair and make_tuple do) can use these traits to obtain T& from reference_wrapper<T>.

See Also

  • std::any: type-erased value storage; holds copies, so use reference_wrapper inside to store references
  • std::atomic: synchronization when a reference is shared across threads
  • std::function, std::bind, std::invoke, std::tie, std::optional for related functional and vocabulary types