primers

As mentioned earlier, the STD :: Move argument _Tp&& looks like an rvalue reference, but can accept an lvalue when used.

  //std::move  
  template<typename _Tp>
    constexpr typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
Copy the code
  int main(a){
      std::string a = "hello";
      std::string&& b = std::move(a); // The argument a is an lvalue
  }
Copy the code

In fact, the parameter T&& in this case is a Universal reference, or Universal References. This section mostly covers template programming, but in order to navigate the various C++ open source libraries, it’s important to understand the concepts of universal references and perfect forwarding.

Universal reference and reference folding

If a function template argument is of type T&&, where T needs to be derived, then T&& is an undefined reference type, called universal reference, that can bind both rvalues and lvalues. Note that && is a universal reference only when automatic type inference occurs (such as automatic type inference for function templates, or the auto keyword). To take a simple example,

template<typename T>
void func(T&& param){}int main(a){
    1 / / examples
    func(1); //1 is an rvalue, param is an rvalue reference
    int a = 2;
    func(a); //a is an lvalue, param is an lvalue reference

    2 / / examples
    std::string b = "hello"; 
    auto&& c = b; //auto&& binds an lvalue
    auto&& d = "world"; //auto&& bind rvalue
}
Copy the code

In the example, T is a template, so T could be int or int& or int&&, and the final argument could become (int&& && param). L2L, L2R, R2L, and R2R are collapsed to a single reference (reference collapsing) because reference to reference is not currently allowable.

  • T&&, T&&, T&& all fold into T&
  • T&& && folds into T&&

This is easy to remember, because whenever an lvalue reference occurs, it will eventually collapse to an lvalue reference.

Perfect forward

With the above concepts in mind, Perfect Forwarding is easy to understand. Universal reference + reference folding + STD :: Forward together form a perfect forwarding mechanism.

To put it simply, STD :: Forward passes the input argument to the next function exactly as it is, if it is an lvalue, or rvalue if it is an rvalue.

The so-called perfect means that it can not only accurately forward the value of the parameter, but also ensure that its left and right value attributes remain unchanged.

Why is it needed?

So what does this code say?

template<typename T>
void func(T& param) {
    cout << "Passing in an lvalue" << endl;
}
template<typename T>
void func(T&& param) {
    cout << "Passed rvalue" << endl;
}

template<typename T>
void test(T&& t) { // argument t, universal reference
    func(t);
}

int main(a) {
    int a = 1;
    test(a);
    test(1);
}
Copy the code

The output is:

Passing in an lvalueCopy the code

If you pass an lvalue to an rvalue, you end up calling the lvalue function, not as expected.

This is because, regardless of whether the test function template is passed an lvalue or an rvalue, the argument t inside the function has its own name and can get an address, so it will always be an lvalue. That is, the argument t passed to the func function is always an lvalue. (The declared lvalue reference and rvalue reference are themselves an lvalue, addressable)

With STD :: Forward in mind, we can modify the test function to make it perfect enough.

template<typename T>
void test(T&& param) {
    func(std::forward<T>(param));
}
Copy the code

At this point, you should understand C++ ‘s motivation for perfect forwarding.

In many C++ scenarios, perfect forwarding of a parameter directly determines whether the parameter is passed using move semantics or copy semantics.

Finally, take a look at the STD ::forward function definition:

  template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); }
Copy the code

Typename STD ::remove_reference<_Tp>::type removes references to _Tp and keeps only the type. We simplify the template code according to the reference type of _Tp:

  // Case 1: receive lvalue, _Tp is derived to string&, then _Tp&& is string& &, collapsed to string&
  string& forward(string& __t) {
    return static_cast<string&>(__t); 
  }

  // Case 2: receiving an rvalue, _Tp is derived to string&&, so _Tp&& is string&& &&, collapsed to string&&
  string&& forward(string& __t) {
    return static_cast<string&&>(__t); 
  }
Copy the code

conclusion

So far, we’ve covered mobile semantics and perfect forwarding, and the role of rvalue references is to support these mechanisms.

After all, what is the point of these new features? C++11 is so complicated, is it unnecessary?

These features are by no means optional, but are designed to reduce the extra copying overhead and improve code performance. But it does increase the cost of understanding, after all, these features, the most proficient in other programming languages, just look at the source can not be fully understood. But once you understand the design motivation, it’s easy to explain.