Have you heard about std::ref
and std::cref
? The helper functions that generate objects of type std::reference_wrapper
? The answer is probably yes. In that case, this article is probably not for you. But if you haven’t heard about them, or the only usage of std::reference_wrapper
you faced was storing references in a vector, then probably it’s worth reading on.
This article is inspired by some failing tests that needed me to use std::ref
in order to pass them.
What does reference_wrapper
do?
A reference of an object T
(T&
) is not copy assignable. On the other hand, std::reference_wrapper<T>
which emulates T&
it both copy-constructible and copy-assignable. It’s even trivially copyable, so copying can take place on a byte level which makes it very efficient.
So when should we use such a wrapper?
Store references in a container
If you ever wanted to store references in a vector, you probably know that this wouldn’t compile.
1
2
// This doesn't compile
std::vector<std::string&> v;
Originally, the root cause was that a reference is not assignable. Once a reference is initialized, it cannot be modified to refer to another object. (Pointers can do that.) Therefore you cannot store const
objects either, as they are not reassignable either.
1
2
// This doesn't compile either
std::vector<const std::string> v;
While the above is still not possible, the rules changed a bit since C++11 and it’s not mainly about copy assignability, but it’s about (not) being erasable.
Erasable means that the following expression is well formed:
1
allocator_traits<A>::destroy(m, p)
Where A
is the container’s allocator type, m
is an allocator instance and p
is a pointer of type *T
. See here for Erasable definition.
By default, std::allocator<T>
is used as vector
’s allocator. With the default allocator, the requirement is equivalent to the validity of p->~T()
(Note that T
is a reference type and p
is pointer to a reference). However, a pointer to a reference is illegal, hence the expression is not well formed.
On the other hand, a pointer to an instance of std::reference_wrapper
is valid and we can store references in a container like this:
1
2
3
4
5
6
std::string s1{"Hello"};
std::string s2{","};
std::string s3{"World!"};
std::vector<std::reference_wrapper<std::string>> v {
std::ref(s1), std::ref(s2), std::ref(s3)
};
Pass references to some standard template functions
But std::ref
and std::cref
doesn’t only come in handy with containers. They are also very useful when you need to pass a reference to std::bind
, to the constructor of std::thread
or to some standard helper functions such as std::make_pair
.
The common characteristic of them is that even if you pass references to them, they will remove those/decay those references and they will either move or copy what you passed in. Therefore if you really want to do as if you passed in a reference, use a reference wrapper!
Let’s have a simple example of how std::ref
/std::cref
makes a difference in such scenarios!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <functional>
#include <iostream>
void f(int& p1, int& p2, const int& p3) {
std::cout << "In function: " << p1 << ' ' << p2 << ' ' << p3 << '\n';
++p1; // increments the copy of n1 stored in the function object
++p2; // increments the main()'s n2
// ++p3; // compile error
}
int main() {
int n1 = 1, n2 = 2, n3 = 3;
std::function<void()> bound_f = std::bind(f, n1, std::ref(n2), std::cref(n3));
n1 = 10;
n2 = 11;
n3 = 12;
std::cout << "Before calling f() directly: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
f(n1, n2, n3);
std::cout << "After calling f() directly: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
std::cout << "==================\n";
std::cout << "Before calling bound_f(): " << n1 << ' ' << n2 << ' ' << n3 << '\n';
bound_f();
bound_f();
std::cout << "After calling bound_f(): " << n1 << ' ' << n2 << ' ' << n3 << '\n';
}
In this example, f()
takes all the integers by reference. The third one is also const
. If we call f()
directly, we get the desired behaviour, n1
and n2
are “permanently” modified.
On the other hand, the behaviour is different when we invoke f()
with the help of std::function
. When we don’t use std::ref
or std::cref
to bind the arguments, they are simply copied. That’s why even though n1
is seemingly passed as a reference, in bound_f
, p1
still has the value it had when the binding was made. Likewise, it doesn’t increase the value of n1
. But if we call bound_f()
the second time, we can see that p1
’s value has changed. What that means is that std::function
holds on a copy of n1
and that is updated between the calls.
If we want p1
to be an actual reference of n1
, we must pass it with the help of std::ref
, just as we did it for n2
/p2
and for n3
/p3
.
Conclusion
std::ref
and std::cref
are helper functions to std::reference_wrapper
objects. With the help of these constructs, you can store references in standard containers.
You can also pass references to templates and various helper functions such std::bind
, std::thread
or std::make_pair
. Without using them, your code will seemingly work, but instead of taking references, copies might be made.
Connect deeper
If you liked this article, please
- hit on the like button,
- subscribe to my newsletter
- and let’s connect on Twitter!