Search code examples
c++perfect-forwarding

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.


Solution

  • 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.)