Blog 2024 08 21 What is std::ref?
Post
Cancel

What is std::ref?

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

This post is licensed under CC BY 4.0 by the author.