Intro
Perfect forwarding is when a wrapper function template passes lvalueness/ravlueness of its arguments to a wrapped function. To do so std::forward is used:
#include <iostream>
using namespace std;
void Display(int& i){
cout<< i<<" int&"<<endl;
}
void Display(int&& i){
cout<< i<<" int&& called"<<endl;
}
template<class T>
void DisplayWrapper(T&& t){
Display(forward<T>(t));
}
int main(){
int x=5;
DisplayWrapper(5); // int&& called
DisplayWrapper(x); // int&
return 0;}
In the above example, DisplayWrapper passes prvalue, 5, and lvalue x to the correct overload of Display. This desired behaviour is conducted with std::forward.
For more details read the following sections.
Prerequisite
Here I assume, you are familiar with lvalue, rvalue, rvalue reference and std::move. If not, I recommend google them or have a quick look at my post here where I briefly explained them all together.
Problem
If we run the above example without std::forward, we have:
template<class T>
void DisplayWrapper(T&& t){
Display(t);
}
The outcome will be:
DisplayWrapper(5); // 5 int&
DisplayWrapper(x); // 5 int&
So DisplayWrapper doesn’t pass 5 as a prvalue to Display function but as an lvalue.
Overloading
Let’s only focus on Display functions, and see which overload is selected with different parameters:
#include <iostream>
using namespace std;
void Display(int& i){
cout<< i<<" int& called"<<endl;
}
void Display(int&& i){
cout<< i<<" int&& called"<<endl;
}
int main(){
int x=5;
int&& y=10;
Display(5); // int&& called
Display(move(y)); // int&& called
Display(move(x)); // int&& called
Display(x); // int& called
Display(y); // int& called
return 0;
}
To sum it up:
- if we pass an lvalue:
xandy, the first overload is called, - if we pass an rvalue: prvalue (
5) and xvalue (move(x)andmove(y)), the second overload is called.
Note that y is a named rvalue reference, so it is an lvalue. On the other hand, the outcome of std::move is an unnamed rvalue reference, therefore, it’s
an rvalue (or technically xvalue).
Universal reference
In a function with the template parameter T, the argument T&& is called universal reference as it can be lvalue reference or rvalue reference.
void Display(int& i){
cout<< i<<" int& called"<<endl;
}
void Display(int&& i){
cout<< i<<" int&& called"<<endl;
}
template<class T>
void DisplayWrapper(T&& t){
Display(t);
}
int main(){
int x=5;
DisplayWrapper(x); // line 1: Display(int&) called
DisplayWrapper(5); // line 2: Display(int&) called!!!
return 0;
}
So at line 1, t can bind to x, then it has the type of int&, lvalue reference. At line 2, t bind to 5 and its type is int&&, rvalue reference.
When passing t to Display, in the first case the correct overload
Display(int& ) is called. In the second case, still, the same overload is called because t in any case is an lvalue (it has a name and address). So somehow, we lost the rvalueness of 5 within DisplayWrapper.
Solution
To ensure that an rvalue in outer scope is still an rvalue inside the wrapper function, we apply std::forward on the universal reference (see the first example in the Intro section). All in all, std::forward forwards an lvalue as lvalue and an rvalue as rvalue.
Details
The universal reference has only the form of auto&& and T&& where T is a template type. Therefore, vector<T>&&, const T&&, MyClass<T>&& are not universal references, they are rvalue references.
Moreover, for T&& to be a universal reference, T must be deduced by the function call, not class instansiation:
template<class T>
struct A {
void DoIt(T&& t){};
}
A<int> a; // T is decided.
Now a call to a.DoIt is like
void DoIt(int&& t){}
which is not a universal reference.
I feel for everyday coding the info here is enough on the subject, but there are a lot of details. If you are interested, start from references down here.
References
Scott Meyers on Isocpp Modernes C++ cppreference
