Pages

About Me - 关于我

My photo
Madison, WI, United States
Joy Young ~~

2011/04/19

TheTricky Parts of Rvalue Reference and Perfect Forwarding.

I have read two articles about these stuffs. They are good. Here are the links:
I would not like to spend much time whenever I want to look up some useful information from these two articles, so I summarize something useful for my future reference.

1. Rvalue reference is not rvalue but reference
If you try to change the value of rvalue reference, it affects the value of the object it is referencing.

2. move semantic is awesome, but not irreplaceable.

Alternatively, you can use the static_cast keyword to cast an lvalue to an rvalue reference, as shown in the following example:

int main()
{
       MemoryBlock block;
       g(block);
       g(static_cast<MemoryBlock&&>(block));//as good as move
}

3. The compiler treats a named rvalue reference as an lvalue and an unnamed rvalue 
reference as an rvalue.
void g(X&& t); // A
void g(X& t);      // B

void h(X&& t)
{
       g(t);
}

Though t is declared like rvalue reference, but it is named, so g() always treats t as lvalue. If we changed the call to g(std::move(t)) then it would always call the rvalue-reference overload.
 
4. What does std::forward do?
When T is an lvalue reference, std::forward<T> is a no-op: it just returns its argument. 
When we pass a temporary to f, so T is just plain X. In this case,  std::forward<T>(t) is equivalent to static_cast<T&&>(t): it ensures that the argument is forwarded as an rvalue reference
Note that, it does not have anything to do with t. How it works is related to T solely.
5. What does std::move do? 
 Based on my understanding alone, its function is nothing more than a static_cast<T&&>()
Here I found some evidence: 
template<typename T>
remove_reference<T>::type&& move(T&& a)
{
       return a;
}

By Jonathan Caves

Jonathan Caves - Visual C++ Compiler Team

remove_reference here is a class from boost, it removes the reference symbol & and add two more instead!

6.  Perfect forwarding is unique to template functions: we can't do this with a non-template function such as h.
To understand this, let’s see how the template deduce the parameters:
First of all, we need to write a template this way:
template <typename T> void print_type_and_value(T&& t)

The tricky part is the T&& in the function. According to different form of T, the T&& will generate different types. See:
int main()
{
      // The following call resolves to:
      // print_type_and_value<string&>(string& && t)
      // Which collapses to:
      // print_type_and_value<string&>(string& t)
      string s1("first");
      print_type_and_value(s1);

      // The following call resolves to:
      // print_type_and_value<const string&>(const string& && t)
      // Which collapses to:
      // print_type_and_value<const string&>(const string& t)
      const string s2("second");
      print_type_and_value(s2);

      // The following call resolves to:
      // print_type_and_value<string&&>(string&& t)
      print_type_and_value(string("third"));

      // The following call resolves to:
      // print_type_and_value<const string&&>(const string&& t)
      print_type_and_value(fourth());
}

The deduction rule:


Expanded type
Collapsed type
T& &
T&
T& &&
T&
T&& &
T&
T&& &&
T&&

Now let’s go back to the discussion of trying perfect forwarding in non-template function:
Within a function that takes its arguments as rvalue references, the named parameter is treated as an lvalue reference. Consequently the call to g(t) from h always calls the lvalue overload. If we changed the call to g(std::forward<X>(t)) then it would always call the rvalue-reference overload. The only way to do this with "normal" functions is to create two overloads: one for lvalues and one for rvalues.
Finally, I attached this piece of code for quick testing.

#include <iostream>
using namespace std;
struct X{int i;};
void g(X&& t){ t.i = -1; cout<<"&&"<<endl;}; // A
void g(X& t){ t.i = -2; cout<<"&"<<endl;}      // B

template<typename T>
void f(T&& t){    g(std::forward<T>(t));}
void h1(X&& t){   g(t);}
void h2(X& t){    g(std::forward<X>(t));}
void h3(X& t){    g(std::forward<X&>(t));}
void h4(X& t){    g(std::move(t));}

int main()
{
     
      X x;
      x.i = 0;
      f(x);   // 1
      f(X()); // 2
      h1(X()); // 3
      h2(x); // 3
      cout<<x.i<<endl;
      h3(x);
      cout<<x.i<<endl;
      h4(x);
      cout<<x.i<<endl;
      getchar();
}










No comments:

Post a Comment