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.