C++11 Universal References and std::forward
Short explanation of why std::forward is necessary
This is assuming you already know what forwarding/universal references are (as a result of reference collapsing rules, (T&)&& -> T& and (T)&& -> T&&). I was scratching my head for a while about the purpose of std::forward, so I’m writing this post to share understanding of it.
void move_or_copy(VisibleCopy<int>& p) {
VisibleCopy<int> c = p; // Simulate a copy.
}
void move_or_copy(VisibleCopy<int>&& p) {
VisibleCopy<int> c = std::move(p); // Simulate a move from p.
}
template<typename T>
void fun(T&& param) {
move_or_copy(param);
}
int main(int argc, char** argv) {
VisibleCopy<int> s = 4;
fun(s);
std::cout << s.Get() << std::endl;
fun(std::move(s));
std::cout << s.Get() << std::endl;
fun(VisibleCopy<int>(4));
}
VisibleCopy<T> is a wrapper type that prints move and copy operations, it also modifies the other value on moves.
Because param is an lvalue, it will use move_or_copy(VisibleCopy<int>&) (i.e copy). It would be unreasonable for param to use move_or_copy(VisibleCopy<int>&&) (i.e move), because we didn’t explicitly say that it should be moved from (with std::move) at that time within fun, we might want to move from it later within fun.
As it stands this prints the following
Copy 4
4
Copy 4
4
Copy 4
A lot of unnecessary copies! When calling it like fun(std::move(s)) or fun(VisibleCopy<int>(4)) we are allowed to move from the parameter, thus preventing expensive copies. So let’s change the body of fun to always move with overload(std::move(param));
Now the output will be the following
Move 4
0
Move 0
0
Move 4
The last two calls where moved properly, but we also moved when calling it like fun(s), we didn’t explicitly tell it to be moved from! The variable s being reset at this point would be highly unexpected and possibly UB (imagine if it contained a RAII value or manual memory management, we would get double-free problems). We want the first call foo(s) (which is fun<int&>(VisibleCopy<int>&)) to use the copy overload, while fun(std::move(s)) and fun(VisibleCopy<int>(4)) (which are fun<int>(VisibleCopy<int>&&)).
And this is exactly what std::forward<T> does. When calling std::forward<T> while T = int&, it will instantiate as int& forward(int& t). Meanwhile when T = int&&, it will instantiate as int&& forward(int& t).
Thus replacing the call in fun with overload(std::forward<T>(param)), it will output the following
Copy 4
4
Move 4
0
Move 4
It copies when it should, and moves when it should (explicit std::move) and when it can (temporary).
The most apparent use-case for this optimization is when the forwarding/universal reference variables are used to construct a new object and you want to use the most suitable method (copy or move) dependning on what was passed into the function.
Written