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:
x
andy
, 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
Latest Posts
- A C++ MPI code for 2D unstructured halo exchange
- Essential bash customizations: prompt, ls, aliases, and history date
- Script to copy a directory path in memory in Bash terminal
- What is the difference between .bashrc, .bash_profile, and .profile?
- A C++ MPI code for 2D arbitrary-thickness halo exchange with sparse blocks