`std::ref` and `std::reference_wrapper` in C++
In refactoring legacy C++ codebases we often have to deal with a lot of functions or class methods that takes a pointer as an argument and then does a bunch of null checks. This is a common pattern in C++ codebases that are not modernized yet.
Modern C++ has introduced a few utilities to help with this pattern. One of them is std::ref
and std::reference_wrapper
. In this post, I wanted to talk about these tools and how they can improve the safety and readability of modern C++ code.
Introduction to std::reference_wrapper
and std::ref
std::reference_wrapper<T>
is a class template that wraps a reference in a copyable, assignable object. It is frequently used in conjunction with std::ref
to pass references to functions that take arguments by value. It simulates &T
and improves null safety by allowing modern C++ code to skip using pointers.
std::ref
is a function template that returns a std::reference_wrapper<T>
object. It is used to pass references to functions that take arguments by value. It is a simple wrapper around std::reference_wrapper<T>
that allows for more concise code. The const analog of std::ref
is std::cref
, which does similar things but returns a std::reference_wrapper<const T>
object.
Let’s look at an example to see why they are needed:
template<typename T>
void increment(T n) {
n++;
}
int main() {
int n = 0;
increment(n);
std::cout << n << std::endl; // prints 0
return 0;
}
It is no surprise that this code will print 0
because n
is passed by value to increment
function. Now, let’s try to give it a reference:
template<typename T>
void increment(T n) {
n++;
}
int main()
{
int n = 0;
int& ref = n;
increment(ref);
std::cout << n << std::endl;
return 0;
}
What do we get at the end here? Surprisingly we still get 0
. This is because although ref
is a reference, it is passed by value to increment
function since increment
takes T
by value. You will see the same behavior even if we pass in &n
to increment
.
This is where std::ref
and std::reference_wrapper
comes in. By wrapping n
with std::ref
, we can pass n
by reference to increment
function:
template<typename T>
void increment(T n) {
n++;
}
int main()
{
int n = 0;
auto ref = std::ref(n);
increment(ref);
std::cout << n << std::endl; // prints 1
return 0;
}
As expected, this will print 1. The ref
is a std::reference_wrapper<int>
object that wraps n
and allows us to pass n
by reference to increment
function. You can think of std::reference_wrapper
as a pointer that is guaranteed to be non-null. the type T
becomes int&
in this case. inside increment
function, and everything works as expected.
Real World Use Cases
Make a container of reference types
std::reference_wrapper
is useful when you want to make a container of reference types. For example, you can make a vector of references to integers:
std::vector<int> v = {1, 2, 3, 4, 5};
std::vector<std::reference_wrapper<int>> v_ref(v.begin(), v.end());
for (auto& i : v_ref) {
i++;
}
for (auto i : v) {
std::cout << i << " ";
}
// prints 2 3 4 5 6
Packaging arguments in various queues or other containers to functions that take by reference
std::reference_wrapper
is also useful when you want to package arguments in various queues or other containers to functions that take by reference. For example, you can use std::reference_wrapper
to pass arguments to a function that takes by reference:
void foo(int& n) {
n++;
}
int main() {
int n = 0;
std::queue<std::reference_wrapper<int>> q;
q.push(n);
foo(q.front());
std::cout << n << std::endl; // prints 1
return 0;
}
This is particularly useful when combined with std::make_tuple
or std::tie
to pass multiple arguments to a function that takes by reference.
Example:
void foo(int& a, int& b) {
a++;
b++;
}
int main() {
int a = 0;
int b = 0;
auto t = std::make_tuple(std::ref(a), std::ref(b));
// forward tuple as args
std::apply(foo, t);
std::cout << a << " " << b << std::endl; // prints 1 1
return 0;
}
Conclusion
std::reference_wrapper
and std::ref
are useful tools in modern C++ to pass references to functions that take arguments by value. They are particularly useful when you want to make a container of reference types or package arguments in various queues or other containers to functions that take by reference. They are a great way to improve the safety and readability of modern C++ code.
References
- std::reference_wrapper and std::ref on cppreference.com
- std::apply on cppreference.com
- std::reference_wrapper common use cases
- stack overlfow post on std::reference_wrapper