Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't my forward_ function work for rvalues?

I've understood how std::move works and implemented my own version for practice only. Now I'm trying to understand how std::forward works:

I've implemented this so far:

#include <iostream>


template <typename T>
T&& forward_(T&& x)
{
    return static_cast<T&&>(x);
}


/*template <typename T>
T&& forward_(T& x)
{
    return static_cast<T&&>(x);
}*/

void incr(int& i)
{
    ++i;
}

void incr2(int x)
{
    ++x;
}

void incr3(int&& x)
{
    ++x;
}

template <typename T, typename F>
void call(T&& a, F func)
{
    func(forward_<T>(a));
}


int main()
{

    int i = 10;
    std::cout << i << '\n';
    call(i, incr);
    std::cout << i << '\n';

    call(i, incr2);
    std::cout << i << '\n';

    call(0, incr3); // Error: cannot bind rvalue reference of type int&& to lvalue of type int.

    std::cout << "\ndone!\n";
}

Why must I provide the overloaded forward(T&) version taking an lvalue reference? As I understand it a forwarding reference can yield an lvalue or an rvalue depending on the type of its argument. So passing the prvalue literal 0 to call along with the incr3 function that takes an rvalue reference of type int&& normally doesn't need forward<T>(T&)?!

  • If I un-comment the forward_(T&) version it works fine!?

  • I'm still confused about: why if I only use the forward_(T&) version does it work for any value category? Then what is the point in having the one taking a forwarding reference forward_(T&&)?

  • If I un-comment the version taking lvalue reference to T& and the one taking forwarding reference T&& then the code works fine and I've added some messages inside both to check which one called. the result is the the one with T&& never called!

      template <typename T>
      T&& forward_(T& x)
      {
          std::cout << "forward_(T&)\n";
          return static_cast<T&&>(x);
      }
    
      template <typename T>
      T&& forward_(T&& x)
      {
          std::cout << "forward_(T&&)\n";
          return static_cast<T&&>(x);
      }
    
  • I mean running the same code in the driver program I've shown above.

like image 312
Maestro Avatar asked Dec 01 '20 20:12

Maestro


2 Answers

A T&& reference stops being a forwarding reference if you manually specify T (instead of letting the compiler deduce it). If the T is not an lvalue reference, then T&& is an rvalue reference and won't accept lvalues.

For example, if you do forward_<int>(...), then the parameter is an rvalue reference and ... can only be an rvalue.

But if you do forward_(...), then the parameter is a forwarding reference and ... can have any value category. (Calling it like this makes no sense though, since forward_(x) will have the same value category as x itself.)

like image 84
HolyBlackCat Avatar answered Oct 04 '22 18:10

HolyBlackCat


It is clear that you wander why having two versions of std::forward; one takes an l-value reference to the type parameter T& and the other takes a universal reference (forwarding) to the type parameter. T&&.

In your case you are using forward_ from inside the function template call which has forwarding reference too. The problem is that even that function call called with an rvalue it always uses forward_ for an lvalue because there's no way that call can pass its arguments without an object (parameter). Remember that a name of an object is an lvlaue even if it's initialized from an r-value. That is why always in your example forward_(T&) is called.

  • Now you ask why there's second version taking forwarding reference? It is so simple and as you may have already guessed: it is used for r-values (the values not the names of those objects).

Here is an example:

template <typename T>
T&& forward_(T& x)
{
    std::cout << "forward_(T&)\n";
    return static_cast<T&&>(x);
}

template <typename T>
T&& forward_(T&& x)
{
    std::cout << "forward_(T&&)\n";
    return static_cast<T&&>(x);
}

int main()
{

    int i = 10;
    forward_(i); // forward(T&)  (1)
    forward_(5); // forward(T&&) (2)
    forward_("Hi"); // forward(T&) (3)
}
like image 35
Raindrop7 Avatar answered Oct 04 '22 19:10

Raindrop7