c++referencereference-wrapper

What is the advantage of std::ref and std::reference_wrapper compared to regular references?


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 a reference to 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

  • 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:

    1. A reference_wrapper object (here r) can be used to create an array of references which was not possible with T&.

    2. r actually acts like a real reference (see how r.get()=70 changed the value of y).

    3. r is not same as T& but r.get() is. This means that r holds T& ie as its name suggests is a wrapper around a reference T&.

    I hope this answer is more than enough to explain your doubts.