Reference Wrappers

Earlier I alluded to some limitations of pure C++ references: they must always be initialized, most containers cannot hold references, and references cannot be rebound.

Here's an example:

class MyList::iterator {
private:
    MyList& parent;
    // we must either set the value of a reference when its declared
    // or set the reference in a constructor initializer list (done below)
    //  if the reference is a class member

    size_t index;
public:
    iterator(MyList& owner, size_t idx) : parent(owner), index(idx) {}

    iterator& operator=(const iterator& other) {
        // ...
    }
};

// ...

    MyList a = //...
    MyList b = //...

    MyList::iterator it = a.begin();
    it = b.begin();

References must either be assigned a value when they are declared or, in the case of references as class members, they can also be initialized in a constructor initializer list.

Now an iterator is something you'd expect to be able to be copied. But reference members (along with const members) prevent the use of compiler generated copy operations. To see why, try to come up with a way to define operator= that makes sense.

You might first think to define it like so:

iterator& operator=(const iterator& other) {
    index = other.index;
    parent = other.parent;
    return *this;
}

But now, what happens when we assign it = b.begin()? Well, we actually change the data of a to be that of b through the line parent = other.parent. So instead of copying iterators, we ended up copying the entire list and overwriting a's data.

So references cannot be copied. This is one of the reasons (along with the inability to have pointers to references) that containers can't store references.

This doesn't mean that if we need references with value semantics that we are forced to use old-style pointers. We can use reference_wrapper.

A reference_wrapper acts like a non-owning pointer. It can be rebound and copied. It's interface is quite simple, you can use the get() member method to get a reference to the data it wraps, and if it holds a reference to a callable object, you can invoke operator() on the reference_wrapper directly. A reference_wrapper, like a smart pointer is actually a template class where its template type is the type of the reference it holds.

int n = 5;
std::reference_wrapper n_ref = n;
// same as: 
// std::reference_wrapper<int> n_ref(n);

int n2 = 10;
auto n2_ref = std::ref(n2); 
// convenience function for creating reference wrappers

n_ref = n2_ref;
n_ref.get() = 20;
// n is still 5
// n2 is now 20
n2_ref.get(); // 20


auto foo(int x) {
    return 5 * x;
}

auto foo_ref = std::ref(foo);
foo_ref(10); // 50

As seen in these examples, instead of calling the constructor directly, its more common to use the std::ref function to create a reference_wrapper. This function is a template too, but the compiler can easily deduce the template type parameter based on the type of the argument we pass to it.

As a quick aside, I want to mention the type of foo_ref; it's std::reference_wrapper<int(int)>. The int(int) is the type of a function that returns an int and has one integer argument. You may recall that the type of a function pointer to such a function would be the same thing with an added "(*)": int(*)(int). This is very much like every other type, however for functions we need the parentheses around the asterisk so we know that it's a function pointer and not a function returning an int *. So then void() would be the type of a function that has no arguments and no return value, and std::string(const char *, const std::vector<char>&) would be the type of a function that returns a string and takes a const char * and reference to a vector of characters.

Returning back to reference_wrapper: let's say, in the spirit of const correctness, that we don't want to change the data the reference refers to and so we want to make the reference wrapper const. The first attempt at this might be to just add const to the type declaration like a normal reference.

const auto n = 10;
const auto m = 20;
const auto r = std::ref(n);

r = std::ref(m); //compiler error!

r.get() = 50; // and this still works!

But when we do that, we're making the reference_wrapper itself be const, and not the data it refers to. This may be desireable in some situations when we don't want to be able to rebind the reference through operator=, but it isn't at all what we intended here.

Just like smart pointers, the solution here is to make the template type be const. So instead of reference_wrapper<int>, we want reference_wrapper<const int>. Intuitively you can think of this as the data being constant versus the reference itself (which has its own data stored in a different are of memory) being constant.

The standard library provides a nice std::cref() function for constructing const reference wrappers. As with smart pointers, std::reference_wrapper<int> and std::reference_wrapper<const int> are not the same type.

int n = 2;
auto r = std::cref(n);
int m = 5;

r.get() = 10; //error, const 

r = std::cref(m); // works

auto j = std::ref(n);

r = j;
// This does a bit more than before
// This first uses an implicit conversion constructor and make a
//  new std::reference_wrapper<const int>
// Then it will call operator= on this newly created object
//
// As we'll see later, move semantics and rvalue reference overloads
// allows this process to be relatively efficient.

The standard library does a pretty good job making templates of const and non-const types, along with templates of polymorphic types behave like normal via conversion constructors, but it's important to remember that these templates do not have the exact same relationship they would if we used references directly.

auto foo(std::reference_wrapper<int>);
auto bar(std::reference_wrapper<int>&);

int n = 10;
auto rf = std::ref(n);
auto crf = std::cref(n);

foo(rf);
foo(crf);
// works, in both cases we construct a new 
// std::reference_wrapper<int>
// which is relatively cheap

bar(rf);
bar(crf); 
//^ doesn't work since crf is a reference_wrapper<const int>

std::reference_wrapper does no lifetime checking, and its up to you to ensure that the data it refers to is not destroyed while the reference is in use. The main advantage reference_wrappers have over raw pointers is that they are unambiguous. It's very clear that a reference wrapper doesn't own the data it refers to, and a reference wrapper to an array will have a different type than a reference wrapper to a single value. But to reiterate, reference wrappers have no mechanisms to know if they're still valid.