Pointers and Memory Management
Perfect Forwarding
std::forward and forwarding references — preserving value category through templates
Interview: Advanced template topic — understanding universal references, std::forward, and why factory functions need it
Perfect Forwarding
Perfect forwarding is the technique of passing arguments through a template function to another function while preserving their original value category (lvalue or rvalue). Without it, passing a temporary (rvalue) through a wrapper function would decay it to an lvalue, losing move-semantic benefits.
The Problem Without Forwarding
Inside a function, even an rvalue reference parameter is itself an lvalue (named variables are lvalues). If you pass it along without std::forward, it's treated as an lvalue — the move constructor of the target never triggers. You'd need 2ⁿ overloads for n parameters to handle all combinations of lvalue/rvalue — perfect forwarding collapses this to one template.
Universal References (Forwarding References)
T&& in a template context (where T is a deduced type) is a universal reference — it can bind to both lvalues and rvalues. If passed an lvalue, T deduces to T& (and T& && collapses to T& by reference collapsing rules). If passed an rvalue, T deduces to T (and T && stays T&&). std::forward<T> then casts to T& or T&& accordingly.
std::forward vs std::move
std::move: unconditionally casts to rvalue — use when you want to move out of a variable. std::forward<T>: conditionally casts to rvalue only if T was deduced as an rvalue type — use in forwarding functions to preserve the original caller's intent.
Interview Corner
Q: What is the difference between T&& in a template vs in a concrete function?
A: In a concrete function like void f(int&& x), T&& is an rvalue reference — it only binds to rvalues. In a template like template<typename T> void f(T&& x), T&& is a forwarding reference — it binds to both lvalues and rvalues via type deduction. The distinction is whether T is a deduced template parameter.
Q: Why does make_unique need perfect forwarding?
A: make_unique<T>(args...) must construct a T with the given arguments. Without perfect forwarding, rvalue arguments would be treated as lvalues inside make_unique, preventing move construction of T. With perfect forwarding: new T(std::forward<Args>(args)...) — lvalues are copied, rvalues are moved, exactly matching a direct call to T's constructor.
Common Pitfalls
- Forwarding a parameter more than once: After forwarding a parameter (which may move it), using it again is undefined behavior. Only forward a parameter in its final use.
- Using std::move instead of std::forward in a forwarding wrapper: This always moves, even lvalues — the caller's lvalue gets silently emptied.
- Universal references with non-deduced T: If T is not deduced (e.g., explicitly specified), T&& is just an rvalue reference, not a universal reference.
Best Practices
- Use
std::forward<T>(arg)in forwarding functions — neverstd::moveunless you specifically want to always move. - Only forward a variadic pack element once — document which use is the "forwarding" use.
- Use
auto&&in range-based for to get a forwarding reference to each element when writing generic code.