The C++ standard library containers are nice. Like, really nice. As the kind of person who worries about performance to the point that they were shy of anything other than a raw data array I've come to appreciate the usability (and speed!) of ye olde std::vector
. Does that many me sound like a wannabe oldie?
Recently I came across a situation where I had, say, a vector [a, b, c, d]
and wanted to create the vectors [a, b, c]
, [a, b, d]
, [a, c, d]
, and [b, c, d]
. I didn't want to actually allocate O(N(N-1))
additional memory but learned that std::vector
didn't support holding references. That's when I came across std::reference_wrapper
.
From the Cppreference page:
std::reference_wrapper
is a class template that wraps a reference in a copyable, assignable object. It is frequently used as a mechanism to store references inside standard containers (likestd::vector
) which cannot normally hold references.
In the example below we will create a vector of int
s, refer to the elements using a vector of reference wrappers, and demonstrate that we can modify either the original vector or the referencing vector.
First, some preamble stuff and intialization of a data array.
#include <iostream>
#include <random>
#include <vector>
int main()
{
size_t N = 5;
std::vector<int> l(N);
std::iota(l.begin(), l.end(), -N/2+1);
// ...
}
Next, we will create a vector of references to the elements of the original vector
// ...
std::vector<std::reference_wrapper<int>> v;
for (int &elt : l) v.emplace_back(elt);
// ...
In the loop a reference wrapper is created for each element in l
and that reference wrapper is appended to the vector v
. Let's verify that the values and addresses of these vectors are identical.
// ...
std::cout << "Elements:\n";
std::cout << "l:\t"; for (int elt : l) std::cout << elt << ' '; std::cout << '\n';
std::cout << "v:\t";for (int elt : v) std::cout << elt << ' '; std::cout << '\n';
std::cout << "Addresses:\n";
std::cout << "l:\t"; for (int &elt : l) std::cout << &elt << ' '; std::cout << '\n';
std::cout << "v:\t"; for (int &elt : v) std::cout << &elt << ' '; std::cout << '\n';
// ...
Compiling and running this code up to this point gives the following output:
[csw.local:tmp] % clang++ --std=c++11 ./ref-wrapper.cpp && ./a.out
Elements:
l: -2 -1 0 1 2
v: -2 -1 0 1 2
Addresses:
l: 0x7fe9834026e0 0x7fe9834026e4 0x7fe9834026e8 0x7fe9834026ec 0x7fe9834026f0
v: 0x7fe9834026e0 0x7fe9834026e4 0x7fe9834026e8 0x7fe9834026ec 0x7fe9834026f0
Now, let's edit the values in the original vector.
// ...
std::cout << "\nDoubling elements of the original vector...\n\n";
for (int &elt : l)
elt *= 2;
std::cout << "Elements:\n";
std::cout << "l:\t"; for (int elt : l) std::cout << elt << ' '; std::cout << '\n';
std::cout << "v:\t"; for (int elt : v) std::cout << elt << ' '; std::cout << '\n';
std::cout << "Addresses:\n";
std::cout << "l:\t"; for (int &elt : l) std::cout << &elt << ' '; std::cout << '\n';
std::cout << "v:\t"; for (int &elt : v) std::cout << &elt << ' '; std::cout << '\n';
// ...
The corresponding output shows that (a) the addresses are the same and (b) the references are working.
Doubling elements of the original vector...
Elements:
l: -4 -2 0 2 4
v: -4 -2 0 2 4
Addresses:
l: 0x7fe9834026e0 0x7fe9834026e4 0x7fe9834026e8 0x7fe9834026ec 0x7fe9834026f0
v: 0x7fe9834026e0 0x7fe9834026e4 0x7fe9834026e8 0x7fe9834026ec 0x7fe9834026f0
We can, of course, modify the elements of the reference vector v
. Since each element points to a corresponding element in l
this is the same as the code above.
// ...
std::cout << "\nDoubling elements of the referencing vector...\n\n";
for (int &elt : v)
elt *= 2;
std::cout << "Elements:\n";
std::cout << "l:\t"; for (int elt : l) std::cout << elt << ' '; std::cout << '\n';
std::cout << "v:\t"; for (int elt : v) std::cout << elt << ' '; std::cout << '\n';
std::cout << "Addresses:\n";
std::cout << "l:\t"; for (int &elt : l) std::cout << &elt << ' '; std::cout << '\n';
std::cout << "v:\t"; for (int &elt : v) std::cout << &elt << ' '; std::cout << '\n';
} // end of int main()
And the output to this block of code.
Doubling elements of the referencing vector...
Elements:
l: -8 -4 0 4 8
v: -8 -4 0 4 8
Addresses:
l: 0x7fe9834026e0 0x7fe9834026e4 0x7fe9834026e8 0x7fe9834026ec 0x7fe9834026f0
v: 0x7fe9834026e0 0x7fe9834026e4 0x7fe9834026e8 0x7fe9834026ec 0x7fe9834026f0