'C++ Difference between std::ref(T) and T&?
I have some questions regarding this program:
#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;
template <typename T> void foo ( T x )
{
auto r=ref(x);
cout<<boolalpha;
cout<<is_same<T&,decltype(r)>::value;
}
int main()
{
int x=5;
foo (x);
return 0;
}
The output is:
false
I want to know, if std::ref
doesn't return the reference of an object, then what does it do? Basically, what is the difference between:
T x;
auto r = ref(x);
and
T x;
T &y = x;
Also, I want to know why does this difference exist? Why do we need std::ref
or std::reference_wrapper
when we have references (i.e. T&
)?
Solution 1:[1]
Well ref
constructs an object of the appropriate reference_wrapper
type to hold a reference to an object. Which means when you apply:
auto r = ref(x);
This returns a reference_wrapper
and not a direct reference to x
(ie T&
). This reference_wrapper
(ie r
) instead holds T&
.
A reference_wrapper
is very useful when you want to emulate a reference
of an object which can be copied (it is both copy-constructible and copy-assignable).
In C++, once you create a reference (say y
) to an object (say x
), then y
and x
share the same base address. Furthermore, y
cannot refer to any other object. Also you cannot create an array of references ie code like this will throw an error:
#include <iostream>
using namespace std;
int main()
{
int x=5, y=7, z=8;
int& arr[] {x,y,z}; // error: declaration of 'arr' as array of references
return 0;
}
However this is legal:
#include <iostream>
#include <functional> // for reference_wrapper
using namespace std;
int main()
{
int x=5, y=7, z=8;
reference_wrapper<int> arr[] {x,y,z};
for (auto a: arr)
cout << a << " ";
return 0;
}
/* OUTPUT:
5 7 8
*/
Talking about your problem with cout << is_same<T&,decltype(r)>::value;
, the solution is:
cout << is_same<T&,decltype(r.get())>::value; // will yield true
Let me show you a program:
#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;
int main()
{
cout << boolalpha;
int x=5, y=7;
reference_wrapper<int> r=x; // or auto r = ref(x);
cout << is_same<int&, decltype(r.get())>::value << "\n";
cout << (&x==&r.get()) << "\n";
r=y;
cout << (&y==&r.get()) << "\n";
r.get()=70;
cout << y;
return 0;
}
/* Ouput:
true
true
true
70
*/
See here we get to know three things:
A
reference_wrapper
object (herer
) can be used to create an array of references which was not possible withT&
.r
actually acts like a real reference (see howr.get()=70
changed the value ofy
).r
is not same asT&
butr.get()
is. This means thatr
holdsT&
ie as its name suggests is a wrapper around a referenceT&
.
I hope this answer is more than enough to explain your doubts.
Solution 2:[2]
std::reference_wrapper
is recognized by standard facilities to be able to pass objects by reference in pass-by-value contexts.
For example, std::bind
can take in the std::ref()
to something, transmit it by value, and unpacks it back into a reference later on.
void print(int i) {
std::cout << i << '\n';
}
int main() {
int i = 10;
auto f1 = std::bind(print, i);
auto f2 = std::bind(print, std::ref(i));
i = 20;
f1();
f2();
}
This snippet outputs :
10
20
The value of i
has been stored (taken by value) into f1
at the point it was initialized, but f2
has kept an std::reference_wrapper
by value, and thus behaves like it took in an int&
.
Solution 3:[3]
A reference (T&
or T&&
) is a special element in C++ language. It allows to manipulate an object by reference and has special use cases in the language. For example, you cannot create a standard container to hold references: vector<T&>
is ill formed and generates a compilation error.
A std::reference_wrapper
on the other hand is a C++ object able to hold a reference. As such, you can use it in standard containers.
std::ref
is a standard function that returns a std::reference_wrapper
on its argument. In the same idea, std::cref
returns std::reference_wrapper
to a const reference.
One interesting property of a std::reference_wrapper
, is that it has an operator T& () const noexcept;
. That means that even if it is a true object, it can be automatically converted to the reference that it is holding. So:
- as it is a copy assignable object, it can be used in containers or in other cases where references are not allowed
- thanks to its
operator T& () const noexcept;
, it can be used anywhere you could use a reference, because it will be automatically converted to it.
Solution 4:[4]
Added an example to show the difference in value you get when you pass the T& and ref(T) arguments in the bind function.
std::bind copies the argument provided unless it is passed by std::ref()/std::cref().
void f(int r1, int& r2, int w1, int& w2)
{
std::cout << r1 << r2 << w1 << w2; // 5 5 10 10
r1 = 9, r2 = 9, w1 = 9, w2 = 9;
}
int main()
{
int w1 = 5, w2 = 5, n1 = 5, n2 = 5;
int& r1 = n1;
int& r2 = n2;
std::function<void()> bound_f = std::bind(f, r1, r2, std::ref(w1), std::ref(w2));
r1 = 10, r2 = 10, w1 = 10, w2 = 10;
bound_f(); // 5 5 10 10
std::cout << r1 << r2 << w1 << w2; // 10 10 10 9
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Jeinzi |
Solution 2 | |
Solution 3 | |
Solution 4 |