Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is std::forward necessary with forwarding references [duplicate]

In a function template like this

template <typename T>
void foo(T&& x) {
  bar(std::forward<T>(x));
}

Isn't x an rvalue reference inside foo, if foo is called with an rvalue reference? If foo is called with an lvalue reference, the cast isn't necessary anyway, because x will also be an lvalue reference inside of foo. Also T will be deduced to the lvalue reference type, and so std::forward<T> won't change the type of x.

I conducted a test using boost::typeindex and I get exactly the same types with and without std::forward<T>.

#include <iostream>
#include <utility>

#include <boost/type_index.hpp>

using std::cout;
using std::endl;

template <typename T> struct __ { };

template <typename T> struct prt_type { };
template <typename T>
std::ostream& operator<<(std::ostream& os, prt_type<T>) {
  os << "\033[1;35m" << boost::typeindex::type_id<T>().pretty_name()
     << "\033[0m";
  return os;
}

template <typename T>
void foo(T&& x) {
  cout << prt_type<__<T>>{} << endl;
  cout << prt_type<__<decltype(x)>>{} << endl;
  cout << prt_type<__<decltype(std::forward<T>(x))>>{} << endl;
  cout << endl;
}

int main(int argc, char* argv[])
{
  foo(1);

  int i = 2;
  foo (i);

  const int j = 3;
  foo(j);

  foo(std::move(i));

  return 0;
}

The output of g++ -Wall test.cc && ./a.out with gcc 6.2.0 and boost 1.62.0 is

__<int>
__<int&&>
__<int&&>

__<int&>
__<int&>
__<int&>

__<int const&>
__<int const&>
__<int const&>

__<int>
__<int&&>
__<int&&>

Edit: I found this answer: https://stackoverflow.com/a/27409428/2640636 Apparently,

as soon as you give a name to the parameter it is an lvalue.

My question is then, why was this behavior chosen over keeping rvalue references as rvalues even when they are given names? It seems to me that the whole forwarding ordeal could be circumvented that way.

Edit2: I'm not asking about what std::forward does. I'm asking about why it's needed.

like image 532
SU3 Avatar asked Feb 18 '17 21:02

SU3


2 Answers

Isn't x an rvalue reference inside foo ?

No, x is a lvalue inside foo (it has a name and an address) of type rvalue reference. Combine that with reference collapsing rules and template type deduction rules and you'll see that you need std::forward to get the right reference type.

Basically, if what you pass to as x is a lvalue, say an int, then T is deduced as int&. Then int && & becomes int& (due to reference collapsing rules), i.e. a lvalue ref.

On the other hand, if you pass a rvalue, say 42, then T is deduced as int, so at the end you have an int&& as the type of x, i.e. a rvalue. Basically that's what std::forward does: casts to T&& the result, like a

static_cast<T&&>(x)

which becomes either T&& or T& due reference collapsing rules.

Its usefulness becomes obvious in generic code, where you may not know in advance whether you'll get a rvalue or lvalue. If you don't invoke std::forward and only do f(x), then x will always be a lvalue, so you'll be losing move semantics when needed and may end up with un-necessary copies etc.

Simple example where you can see the difference:

#include <iostream>

struct X
{
    X() = default;
    X(X&&) {std::cout << "Moving...\n";};
    X(const X&) {std::cout << "Copying...\n";}
};

template <typename T>
void f1(T&& x)
{
    g(std::forward<T>(x));
}

template <typename T>
void f2(T&& x)
{
    g(x);
}

template <typename T>
void g(T x)
{ }

int main()
{
    X x;
    std::cout << "with std::forward\n";
    f1(X{}); // moving

    std::cout << "without std::forward\n";
    f2(X{}); // copying
}

Live on Coliru

like image 54
vsoftco Avatar answered Oct 02 '22 15:10

vsoftco


You really don't want your parameters to be automatically moved to the functions called. Consider this function:

template <typename T>
void foo(T&& x) {
  bar(x);
  baz(x);
  global::y = std::forward<T>(x);
}

Now you really don't want an automatic move to bar and an empty parameter to baz.

The current rules of requiring you to specify if and when to move or forward a parameter are not accidental.

like image 45
Bo Persson Avatar answered Oct 02 '22 16:10

Bo Persson