Perfect forwarding in C++
Xiahua Liu August 28, 2024 #C++Perfect forwarding is a powerful C++ technique used primarily in template programming to preserve the value category (lvalue vs. rvalue) of arguments passed to a function. It is essential for writing generic wrapper functions that are both efficient and correct.
You can find the official documentation on cppreference.com.
The Problem: Losing Value Categories
Imagine you are writing a wrapper function that accepts an argument and passes it to another function foo().
void
void
void
If we pass an expensive object to wrapper, it gets copied into arg. To avoid copying, we might try to take a reference:
void
But now we can't pass rvalues (like temporary objects wrapper(5)) because you cannot bind a non-const lvalue reference to an rvalue.
The Solution: Universal References and std::forward
C++11 introduced "Universal References" (also known as Forwarding References), denoted by T&& in a template context.
void
How std::forward Works
std::forward relies on Reference Collapsing Rules. When you pass an argument to wrapper(T&& arg):
-
If you pass an lvalue (
int x):Tis deduced asint&. The typeT&&becomesint& &&, which collapses toint&.std::forwardcasts it toint&. -
If you pass an rvalue (
5):Tis deduced asint. The typeT&&becomesint&&.std::forwardcasts it toint&&.
This conditional casting ensures that foo receives exactly what was passed to wrapper:
- If
wrapperreceived an lvalue,foosees an lvalue. - If
wrapperreceived an rvalue,foosees an rvalue.
Difference from std::move()
It is easy to confuse std::forward with std::move, but they serve different purposes.
std::move(x): Unconditionally castsxto an rvalue. You use this when you are done with an object and want to transfer ownership.std::forward<T>(x): Conditionally castsxto an rvalue only ifxwas originally an rvalue.
| Feature | std::move | std::forward |
|---|---|---|
| Action | Always casts to rvalue | Casts to rvalue only if T is not an lvalue reference |
| Use Case | Moving ownership | Generic wrappers / Templates |
| Requirement | No template args needed | Requires template type T |
Summary
Use std::forward<T>() exclusively in templates when you have a "Universal Reference" (T&&) and you want to pass the argument to another function exactly as it was received.
Use std::move() when you specifically want to trigger move semantics on a concrete object.