Perfect Forwarding
Master perfect forwarding with std::forward for writing efficient generic code
Learn perfect forwarding - the technique that enables writing generic wrapper functions that preserve argument types and enable optimal performance.
A Simple Example
#include <iostream>
#include <memory>
#include <utility>
class Widget {
public:
Widget() { std::cout << "Widget default constructed\n"; }
Widget(const Widget&) { std::cout << "Widget copied\n"; }
Widget(Widget&&) noexcept { std::cout << "Widget moved\n"; }
};
template<typename T, typename... Args>
std::unique_ptr<T> make_unique_verbose(Args&&... args) {
std::cout << "Creating object...\n";
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
int main() {
Widget w;
auto p1{make_unique_verbose<Widget>()}; // Default construct
auto p2{make_unique_verbose<Widget>(w)}; // Copy
auto p3{make_unique_verbose<Widget>(Widget{})}; // Move
return 0;
}
Breaking It Down
Universal References T&&
-
In template context,
T&&is a universal reference (also called forwarding reference) - Can bind to both lvalues and rvalues, unlike regular rvalue references
-
Type deduction: lvalue argument gives
T&, rvalue argument givesT&& -
Remember: Only
T&&in template context is universal.Widget&&is always an rvalue reference
std::forward<T>() - Perfect Forwarding
- What it does: Forwards arguments preserving their value category (lvalue or rvalue)
-
Usage:
std::forward<T>(arg)inside template functions - Purpose: Enables move semantics to work through wrapper functions
- Remember: Use std::forward with universal references, std::move with rvalue references
How Perfect Forwarding Works
-
Step 1: Universal reference
Args&&...captures argument types - Step 2: std::forward preserves lvalue/rvalue nature
- Step 3: Inner function receives arguments as if called directly
- Remember: Without std::forward, all arguments become lvalues inside the function
Reference Collapsing Rules
-
T& &collapses toT&(lvalue reference) -
T& &&collapses toT&(lvalue reference) -
T&& &collapses toT&(lvalue reference) -
T&& &&collapses toT&&(rvalue reference) - Remember: An lvalue anywhere means lvalue; only rvalue + rvalue gives rvalue
Why This Matters
- When writing factory functions or wrappers, you want to pass arguments efficiently without extra copies.
- Perfect forwarding preserves whether an argument is an lvalue or rvalue, enabling move semantics to work correctly.
- This is the foundation of std::make_unique, std::make_shared, and many other standard library utilities.
- Understanding perfect forwarding unlocks the full power of variadic templates and generic programming.
Critical Insight
Perfect forwarding is like being a perfect messenger. Imagine you are delivering packages - some people hand you their package directly (rvalue), others show you where their package is stored (lvalue). A perfect messenger remembers which is which and delivers accordingly.
Without std::forward, all packages would be treated as if they need to be copied. With std::forward, temporary packages can be moved efficiently, while referenced packages are passed by reference. The inner function receives arguments exactly as if called directly - no performance lost in translation!
Best Practices
Always use std::forward with universal references: When you have T&& in a template, use std::forward<T>() to forward it.
Use std::forward only once per argument: Forwarding consumes the argument. Do not forward the same argument multiple times.
Combine with variadic templates: Perfect forwarding shines with parameter packs Args&&... args and std::forward<Args>(args)....
Understand when to use std::move vs std::forward: Use std::move when you know you have an rvalue, std::forward for universal references.
Common Mistakes
Forgetting std::forward: Without it, all arguments become lvalues and move semantics breaks.
Using std::move instead of std::forward: std::move always gives rvalue, breaking lvalue forwarding.
Forwarding the same argument twice: After forwarding (or moving), do not use the argument again.
Mixing up std::forward with regular rvalue references: std::forward only works correctly with universal references (T&& in templates).
Debug Challenge
This wrapper function forces copies instead of preserving move semantics. Click the highlighted line to fix it:
Quick Quiz
- What is the difference between std::move and std::forward?
- In a template function, what is T&& called?
- Why do we need std::forward?
Practice Playground
Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.
Output:
Error:
Lesson Progress
- Fix This Code
- Quick Quiz
- Practice Playground - run once