Perfect Forwarding
Preserve value categories through template wrappers using forwarding references, std::forward, and reference collapsing.
Perfect Forwardingsince C++11A technique using forwarding references (T&& in a deduced context) and std::forward to propagate the value category β lvalue or rvalue β of arguments unchanged through a template wrapper to a downstream function.
Overview
When a function template receives arguments and passes them elsewhere, the naive approach silently converts rvalue arguments to lvalues β costing moves that become copies. Perfect forwarding solves this by preserving value category through the entire call chain.
The mechanism rests on two C++11 rules working in concert: forwarding references (a T&& parameter where T is a deduced template parameter) and reference collapsing (the rules for merging double references). std::forward<T> then uses the deduced T to decide whether to cast back to an rvalue or leave as an lvalue.
Why Value Category Gets Lost
Named variables are always lvalues, even if they hold rvalue references:
void sink(std::string&& s); // only binds rvalues
void wrapper(std::string&& s) {
sink(s); // error: s is an lvalue β it has a name
sink(std::move(s)); // compiles, but unconditionally moves β wrong for generics
}A generic wrapper must handle both lvalue and rvalue inputs and forward each appropriately. That is exactly what perfect forwarding provides.
Syntax
Forwarding References
T&& is a forwarding reference only when T is a directly deduced template parameter:
template<typename T>
void f(T&& x); // forwarding reference β T is deduced from the call
void g(std::string&& x); // rvalue reference β no deduction
template<typename T>
void h(std::vector<T>&& x); // rvalue reference β T deduced, but parameter is
// std::vector<T>&&, not a plain T&&auto&& is also a forwarding reference wherever auto is deduced (C++11 range-for, C++14 generic lambdas, C++20 abbreviated templates):
for (auto&& elem : container) { /* elem is a forwarding ref */ } // C++11
auto fn = [](auto&& x) { /* x is a forwarding ref */ }; // C++14Reference Collapsing (C++11)
When T is deduced as a reference type, double-references are collapsed:
T deduced as | Parameter T&& collapses to |
|---|---|
int& | int& |
const int& | const int& |
int (non-reference) | int&& |
The rule is simple: any combination containing at least one & collapses to &. Only && + && stays &&.
How std::forward Works
// Conceptual implementation (C++11, simplified)
template<typename T>
T&& forward(std::remove_reference_t<T>& arg) noexcept {
return static_cast<T&&>(arg);
}When T = int&: static_cast<int& &&> β static_cast<int&> β a no-op, returns lvalue.
When T = int: static_cast<int&&> β restores rvalue-ness to the named variable.
Always specify T explicitly. std::forward with a deduced argument type is almost always wrong. std::forward needs the original deduced T from the outer function, not the type of the expression it receives.
Examples
Generic Factory
template<typename T, typename... Args>
T make(Args&&... args) {
return T(std::forward<Args>(args)...);
}
struct Widget {
Widget(const std::string& name, int id); // lvalue overload
Widget(std::string&& name, int id); // rvalue overload
};
std::string name = "foo";
auto w1 = make<Widget>(name, 1); // lvalue: copies name
auto w2 = make<Widget>(std::move(name), 2); // rvalue: moves name
auto w3 = make<Widget>("bar", 3); // rvalue: constructed in-placeThis is the exact pattern behind std::make_unique, std::make_shared (both C++14), and every container's emplace family.
Wrapping Callables
Both the callable and its arguments must be forwarded:
// C++14: decltype(auto) preserves reference return types
template<typename F, typename... Args>
decltype(auto) invoke_logged(F&& f, Args&&... args) {
std::clog << "invoking\n";
return std::forward<F>(f)(std::forward<Args>(args)...);
}Using plain auto return type instead of decltype(auto) would strip references and cv-qualifiers β a subtle correctness bug in wrappers.
LIFT: Wrapping Overload Sets (C++14)
You cannot pass an overloaded function name directly to a higher-order function; the compiler has no argument types to select an overload. A forwarding lambda wraps the entire overload set:
#define LIFT(name) \
[](auto&&... x) \
noexcept(noexcept(name(std::forward<decltype(x)>(x)...))) \
-> decltype(name(std::forward<decltype(x)>(x)...)) \
{ return name(std::forward<decltype(x)>(x)...); }
// std::transform(v.begin(), v.end(), out, foo); // error if foo is overloaded
std::transform(v.begin(), v.end(), out, LIFT(foo)); // okThe noexcept specifier and explicit return type ensure the lambda doesn't accidentally weaken exception guarantees or strip reference returns.
std::forward_as_tuple for Deferred Construction (C++11)
When an argument pack must cross a boundary before the target object is constructed:
template<typename... Args>
void maybe_emplace(std::vector<Widget>& v, bool condition, Args&&... args) {
if (condition) {
v.emplace_back(std::forward<Args>(args)...);
} else {
// capture pack as tuple of references; rvalues become rvalue refs
auto pack = std::forward_as_tuple(std::forward<Args>(args)...);
// ... later:
std::apply([&v](auto&&... a) {
v.emplace_back(std::forward<decltype(a)>(a)...);
}, std::move(pack));
}
}std::forward_as_tuple builds a std::tuple of references, preserving value category. Elements that were rvalues become T&& members in the tuple.
C++20: Abbreviated Templates
With abbreviated function templates, T is not named β use decltype(x) instead:
// C++20 abbreviated template
decltype(auto) wrap(auto&& f, auto&&... args) {
return std::forward<decltype(f)>(f)(
std::forward<decltype(args)>(args)...
);
}
// Generic lambda (C++14+)
auto wrap = [](auto&& f, auto&&... args) -> decltype(auto) {
return std::forward<decltype(f)>(f)(
std::forward<decltype(args)>(args)...
);
};emplace vs push
emplace_back uses perfect forwarding to construct directly inside the container, eliminating the intermediate temporary:
std::vector<std::string> v;
std::string s = "hello";
v.push_back(s); // copy-constructs std::string, then copy into slot
v.push_back(std::move(s)); // move-constructs std::string, then move into slot
v.push_back("world"); // constructs temporary std::string, then moves it
v.emplace_back("world"); // forwards "world" directly to std::string(const char*)
v.emplace_back(5, 'x'); // calls std::string(size_t, char) β no push_back equivalentFor types heavier than std::string, or types with deleted move constructors, emplace_back can be the only viable option.
Best Practices
Return decltype(auto) from wrappers. Plain auto strips references and cv-qualifiers from the return type β a transparent wrapper must use decltype(auto) to propagate them.
Constrain forwarding references. An unconstrained T&& accepts every type, including arguments intended for other overloads. In C++20, use concepts:
// C++20: constrained forwarding ref
template<std::derived_from<Widget> T>
void process(T&& x);
// C++11/14: enable_if
template<typename T, typename = std::enable_if_t<std::is_base_of_v<Widget, std::decay_t<T>>>>
void process(T&& x);Without constraints, a forwarding-reference constructor or function beats the copy constructor for non-const lvalues β a common surprise in class templates.
Forward callables. A functor may have reference-qualified operator() overloads. Always forward the callable itself, not just its arguments.
Common Pitfalls
Forwarding the Same Argument Twice
After std::forward<T>(x) where T is a non-reference, x is in a moved-from state. A second std::forward<T>(x) is undefined behavior:
template<typename T>
void bad(T&& x) {
log(std::forward<T>(x)); // x may be moved here
use(std::forward<T>(x)); // UB when T is a non-reference type
}
template<typename T>
void good(T&& x) {
log(x); // inspect as lvalue β no move
use(std::forward<T>(x)); // forward only at the final consumption point
}std::move on a Forwarding Reference
std::move unconditionally casts to rvalue. Applied to a forwarding reference bound to an lvalue, it silently steals the caller's value:
template<typename T>
void bad(T&& x) {
sink(std::move(x)); // moves even if x was an lvalue β caller loses its object
}
template<typename T>
void good(T&& x) {
sink(std::forward<T>(x)); // moves only if x was originally an rvalue
}Braced-Init-Lists Cannot Deduce
T&& does not help with brace initialization β the compiler has no type to deduce T from:
template<typename T>
void f(T&& x);
f({1, 2, 3}); // error: cannot deduce T
f(std::vector<int>{1, 2, 3}); // ok: T deduces to std::vector<int>Forwarding-Reference Constructors Hijack Copy
A templated constructor with T&& is a better match than the copy constructor for non-const lvalues:
struct S {
template<typename T>
S(T&& x) { /* ... */ } // matches S& better than S(const S&)
};
S a;
S b(a); // calls template constructor with T=S&, not copy constructor!Constrain the template parameter to exclude S and its cv/ref variations, or add enable_if/concept guards.
std::forward_like (C++23)
std::forward_like transfers the value category of one expression onto another type. It is useful when a wrapper owns a member and wants to forward it with the same value category as *this β something std::forward alone cannot express.
// C++23
#include <utility>
template<typename Self, typename T>
decltype(auto) forward_member(Self&& self, T& member) {
return std::forward_like<Self>(member);
// If Self is an rvalue ref β casts member to T&&
// If Self is an lvalue ref β returns member as T&
}
struct Wrapper {
std::string data;
// Propagate the wrapper's own value category to the member
decltype(auto) get(this auto&& self) {
return std::forward_like<decltype(self)>(self.data);
}
};
Wrapper w{"hello"};
std::string a = w.get(); // lvalue Wrapper β copies data
std::string b = std::move(w).get(); // rvalue Wrapper β moves datastd::forward_like is not a drop-in for std::forward. It ignores the actual type of member; it only looks at the value category of the Self template argument. Pair it with deducing-this (this auto&&) for the most natural ownership-propagation pattern.
Forwarding in variadic templates
Variadic forwarding is the backbone of every generic factory and dispatcher in the standard library. The Args&&... pattern combined with std::forward<Args>(args)... expands both the pack and the forward in a single expression.
// C++11
template<typename... Args>
void relay(Args&&... args) {
target(std::forward<Args>(args)...);
// ^ββ expands to: forward<A0>(a0), forward<A1>(a1), ...
}When the pack must be passed to multiple destinations, capture it first with std::forward_as_tuple to avoid forwarding a moved-from argument:
// C++11
template<typename... Args>
void dual_dispatch(Args&&... args) {
auto pack = std::forward_as_tuple(std::forward<Args>(args)...);
std::apply([](auto&&... a) { logger(std::forward<decltype(a)>(a)...); }, pack);
std::apply([](auto&&... a) { target(std::forward<decltype(a)>(a)...); }, std::move(pack));
}Moving pack on the second std::apply call ensures the stored rvalue references are forwarded to target rather than left in an indeterminate state.
Forwarding in emplace operations
emplace methods use perfect forwarding to construct objects directly inside a container slot, avoiding all intermediate temporaries. The container calls std::allocator_traits::construct, which forwards the argument pack to the element's constructor.
// C++11
#include <vector>
#include <map>
struct Point { int x, y, z; };
std::vector<Point> pts;
pts.emplace_back(1, 2, 3); // constructs Point{1,2,3} in-place β zero copies
std::map<int, std::string> m;
m.emplace(42, "hello"); // constructs pair<const int, string> in-place
m.try_emplace(42, 5, 'x'); // C++17: only constructs if key absenttry_emplace (C++17) has an important advantage over emplace: it does not construct the mapped value when the key already exists, so forwarded arguments are never consumed uselessly:
// C++17
std::map<int, std::vector<int>> cache;
cache.try_emplace(key, std::move(expensive_vec));
// If key exists, expensive_vec is NOT moved β it remains validForwarding constructors
A forwarding constructor delegates to another constructor while preserving value categories, enabling efficient wrapper types. The pattern is common in optional-like, expected, and tagged-union types.
// C++11
template<typename T>
class Box {
T value_;
public:
// Forwarding constructor: accepts any argument(s) constructible into T
template<typename... Args,
typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
explicit Box(Args&&... args)
: value_(std::forward<Args>(args)...) {}
};
Box<std::string> b1("hello"); // T constructed from const char*
Box<std::string> b2(5, 'x'); // T constructed from (size_t, char)
std::string s = "world";
Box<std::string> b3(s); // copies s into value_
Box<std::string> b4(std::move(s)); // moves s into value_In C++20, replace enable_if with a concept constraint for cleaner diagnostics:
// C++20
template<typename T>
class Box {
T value_;
public:
template<typename... Args>
requires std::constructible_from<T, Args...>
explicit Box(Args&&... args)
: value_(std::forward<Args>(args)...) {}
};Guard against accidentally matching copy/move construction of Box itself by adding !std::same_as<std::decay_t<FirstArg>, Box> to the constraint.
Common pitfall: forwarding after use
Once an argument has been forwarded as an rvalue, the source object is in a valid-but-unspecified state β any subsequent access or second forward is undefined behaviour or a logical bug. This pitfall is particularly subtle when a forwarded argument also appears in a default argument or a side-effectful expression.
// C++11 β WRONG
template<typename T>
void wrong(T&& x) {
log(std::forward<T>(x)); // may move x into log
store(std::forward<T>(x)); // UB: x may already be moved-from
}
// CORRECT: inspect first, forward last
template<typename T>
void right(T&& x) {
log(x); // read as lvalue β no move
store(std::forward<T>(x)); // safe: single consumption point
}The problem also appears across conditional branches:
// C++11 β WRONG
template<typename T>
void conditional_wrong(T&& x, bool flag) {
auto& ref = some_map[std::forward<T>(x)]; // may move x
if (flag) sink(std::forward<T>(x)); // double-forward
}
// CORRECT: forward at most once per code path
template<typename T>
void conditional_right(T&& x, bool flag) {
auto key = x; // copy for the lookup
auto& ref = some_map[key];
if (flag) sink(std::forward<T>(x)); // single forward
}Perfect forwarding in lambdas (C++20)
C++20 template lambdas allow explicit template parameter lists, making it possible to capture the deduced T and call std::forward<T> correctly inside a lambda β previously impossible without a macro.
// C++14: decltype workaround β verbose but correct
auto wrap14 = [](auto&& x) -> decltype(auto) {
return sink(std::forward<decltype(x)>(x));
};
// C++20: explicit template parameter in lambda
auto wrap20 = []<typename T>(T&& x) -> decltype(auto) {
return sink(std::forward<T>(x)); // T is now in scope
};
// C++20: variadic template lambda
auto relay20 = []<typename... Args>(Args&&... args) {
target(std::forward<Args>(args)...);
};The C++20 form also enables constraints directly on the lambda's template parameter:
// C++20
auto process = []<std::integral T>(T&& val) {
// only accepts integral types, forwarding preserved
consume(std::forward<T>(val));
};Abbreviated forwarding with auto&& (C++20)
auto&& as a function parameter (abbreviated template syntax, C++20) creates a forwarding reference without naming the template parameter, but std::forward still requires a type β use decltype(param) to recover it.
// C++20 abbreviated template (each auto&& is an implicit template parameter)
decltype(auto) wrap(auto&& f, auto&&... args) {
return std::forward<decltype(f)>(f)(
std::forward<decltype(args)>(args)...
);
}This is equivalent to the explicit template form but considerably more concise. The same pattern applies in abbreviated free functions, member functions with this auto&& (deducing-this), and immediately invoked lambdas:
// C++20: abbreviated member function
struct Logger {
decltype(auto) call(this auto&& self, auto&&... args) {
self.log_call();
return std::forward<decltype(self)>(self).impl_(
std::forward<decltype(args)>(args)...
);
}
private:
std::function<void()> impl_;
};Never write std::forward<auto>(x). The keyword auto is not a valid template argument for std::forward; always spell out decltype(x) to extract the deduced type.
See Also
- Move Semantics β the value category model that perfect forwarding preserves
- Variadic Templates β required for forwarding argument packs with
Args&&... - Type Erasure β forwarding is frequently used inside type-erased wrappers
- CRTP β policy-based designs that pair forwarding references with static dispatch
- Deducing This β C++23 explicit object parameters interact with forwarding reference semantics