reference_wrapper provides reference semantics along with the resilience to rebind to a different object.
std::reference_wrapper<T> is a copyable and assignable object that imitates a reference (T&). It gives the non-nullable guarantee of a reference and the pointer-like flexibility to rebind to another object.
The usual way to create an std::reference_wrapper<T> is via std::ref (or std::cref for reference_wrapper<const T>). A contrived example:
template<typename N>
void change(N n) {
//if n is std::reference_wrapper<int>,
// it implicitly converts to int& here.
n += 1;
}
void foo() {
int x = 10;
int& xref = x;
change(xref); //Passed by value
//x is still 10
std::cout << x << "\n"; //10
//Be explicit to pass by reference
change<int&>(x);
//x is 11 now
std::cout << x << "\n"; //11
//Or use std::ref
change(std::ref(x)); //Passed by reference
//x is 12 now
std::cout << x << "\n"; //12
}
Read on for details.
C++ references are favored over pointers wherever possible because they cannot be null, appropriately express by-reference intent, and have better readability because of no dereferencing (*, ->) jumble.
However, a reference is a stubborn alias of an object. That presents some challenges, like the one shown above where the function template parameter type has to be explicitly specified to pass by reference.
Also, we cannot get the address of a reference itself because a reference is not an object (a region of storage) according to the C++ standard. Declaring a reference to a reference, an array of references, and a pointer to a reference is forbidden in C++. Because of that, reference types do not meet the Erasable requirement of STL container elements. Therefore, we cannot have a container (e.g., vector or list) of reference elements:
std::vector<int&> v; //Error
Moreover, a reference cannot rebind to another object. Therefore, assigning a reference to another does not assign the reference itself; it assigns the object:
int x=10, y=20;
int &xref = x, &yref = y;
xref = yref;
//xref still refers x, which is 20 now.
As references cannot rebind, having a reference class member is a pain in the neck because a reference member makes a class non-assignable — the default copy-assignment operator is deleted. Move-semantics does not make sense with a reference member altogether.
An std::reference_wrapper is a copyable and assignable object that emulates a reference. Contrary to its name, it does not wrap a reference. It works by encapsulating a pointer (T*) and by implicitly converting to a reference (T&). It cannot be default constructed or initialized with a temporary; therefore, it cannot be null or invalid:
std::reference_wrapper<int> nr; //Error! must initialize.
std::string str1{"Hello"};
std::string str2{"World"};
auto r1 = std::ref(str1); //OK
auto r2 = std::ref(str2); //OK
//Assignment rebinds the reference_wrapper
r2 = r1; //r2 also refers to str1 now
//Implicit conversion to std::string&
std::string cstr = r2; //cstr is "Hello"
//Possible to create an array of reference_wrapper
std::reference_wrapper<std::string> arr[] = {str1, str2};
auto r2 = std::ref(std::string("Hello")); //Error! no temporary (rvalue) allowed.
The only downside is that to access the members of an object (T), we have to use the 'std::reference_wrapper<T>::get' method:
std::string str{"Hello"};
auto sref = std::ref(str);
//Print length of str
std::cout << sref.get().length() << "\n"; //5
Also, to assign to the referred object use 'get()':
sref.get() = "World"; //str is changed to "World"
std::cout << str << "\n"; //World
Hopefully, using 'get()' would not be necessary when C++ supports overloading operator dot(.) and the concept of smart-references someday. See proposal N4477.
We can have a vector of std::reference_wrapper, have it as a member of a class, and more as we see in the next section.
std::reference_wrapper can be used as an argument to a template function (or constructor) to avoid specifying the template parameter types explicitly. A peculiar case here is of make_pair (make_tuple) whose purpose is to cut the verbosity associated with instantiating a pair (tuple). Compare these:
int m=10, n=20;
std::string s{"Hello"};
std::pair<int&, int> p1(m, n);
std::tuple<int&, int, std::string&> t1(m, n, s);
//Vs.
auto p2 = std::make_pair(std::ref(m), n);
auto t2 = std::make_tuple(std::ref(m), n, std::ref(s));
Another advantage of reference_wrapper is that it cannot be instantiated with a temporary. E.g., the following is an undefined-behavior (UB) because the temporary's life is extended only until the constructor parameter goes out of scope:
std::string yell() { return "hey"; }
std::pair<const std::string&, int> p3(yell(), n); //Bad!
//The temporary std::string("hey") is already destroyed here.
//Accessing p3.first is UB here.
The above case is of dangling reference, which can be avoided with reference_wrapper:
//However, safe with std::ref
auto p4 = std::make_pair(std::ref(yell()), n); //Error!, good
The make_pair and make_tuple are somewhat irrelevant since the C++17's class template argument deduction (CTAD). But the reasons still apply, now, with the constructors:
std::pair p5(std::ref(m), n);
std::tuple t3(std::ref(m), n, std::ref(s));
However, there is a subtlety associated with make_pair and make_tuple, which should not be so important in most cases. The make_pair and make_tuple decay a reference_wrapper<T> to a reference (T&), whereas, that is not the case with CTAD construction of pair and tuple.
Unlike a reference, a reference_wrapper is an object and thus satisfies the STL container element requirements (Erasable, to be precise). Therefore, reference_wrapper can be used as a vector element type.
A reference_wrapper<T> could be a safe alternative to a pointer type (T*) for storing in a vector:
using namespace std;
vector<reference_wrapper<int>> v; //OK
int a=10;
v.push_back(std::ref(a));
We can pass arguments to the start-function when creating a new thread via std::thread(startFunction, args). Those arguments are passed by value from the thread creator function because the std::thread constructor copies or moves the creator's arguments before passing them to the start-function.
So, a start-function's reference parameter cannot bind to a creator's argument. It can only bind to a temporary created by std::thread:
void start(int& i) { i += 1; }
void start_const(const int& i) { }
void create() {
int e = 10;
//e is copied below to a temporary
std::thread(start, e).join(); //Error! can't bind temporary to int&.
std::thread(start_const, e).join(); //OK. But sort of by-value
}
If we want to pass an argument to a start-function by reference, we do that through std::ref, as follows:
void create() {
int e = 10;
std::thread(start, std::ref(e)).join(); //OK. By-ref
//e is 11 now
std::thread(start_const, std::ref(e)).join(); //By-ref
std::thread(start_const, std::cref(e)).join(); //By-ref
}
Above, std::ref generates a reference_wrapper<int
> that eventually implicitly converts to an int&
, thus binding the start(int&)'s reference parameter to the argument passed by the create().
Having a reference class member poses problems, such that, it makes the class non-assignable and practically immovable:
struct W {
W(int& i):iRef(i) {}
int& iRef;
};
int u=10, v=20;
W w1(u);
W w2(v);
w1 = w2; //Error! implicitly deleted copy-assignment operator
The usual practice is to avoid references as class members and use pointers instead.
A reference_wrapper offers the best of both worlds:
struct W {
W(int& i):iRef(i) {}
std::reference_wrapper<int> iRef;
};
W w3(u);
W w4(v);
w3 = w4; //OK
An std::reference_wrapper<T> can be invoked like a function as long as the T is a callable. This feature is particularly useful with STL algorithms if we want to avoid copying a large or stateful function object.
Besides, T can be any callable – a regular function, a lambda, or a function object. For example:
struct Large {
bool operator()(int i) const {
//Filter and process
return true;
}
//big data
};
const Large large; //Large immutable data and function object
std::vector<int> in1; //input vector
std::vector<int> in2; //input vector
void process() {
std::vector<int> out;
//Pass Large by-ref to avoid copy
std::copy_if(in1.begin(), in1.end(), std::back_inserter(out), std::ref(large));
std::copy_if(in2.begin(), in2.end(), std::back_inserter(out), std::ref(large));
//use the filtered 'out' vector
}
std::bind generates a callable wrapper known as bind expression that forwards the call to a wrapped-callable. A bind expression can have some or all of the wrapped-callable's parameters bound.
However, the bound arguments are copied or moved in a bind expression.
void caw(const std::string& quality, const std::string& food) {
std::cout << "A " << quality << " " << food << "\n";
}
using namespace std::placeholders; // for _1, _2
std::string donut("donut");
auto donutcaw = std::bind(caw, _1, donut); //donut is copied
donutcaw("chocolate"); //A chocolate donut
Therefore, we need to use std::ref (or std::cref) if we want to pass the bound parameters by reference:
std::string muffin("muffin");
auto muffincaw = std::bind(caw, _1, std::ref(muffin)); //muffin is passed by-ref
muffincaw("delicious"); //A delicious muffin