49 / 163 · C++11 · 10 min
Forwarding references and perfect forwarding
A forwarding reference, together with template deduction, records the value category the caller passed in; std::forward then uses that information for the next call. It is for transparent wrappers, not an alias for every T&&, and not a more advanced general replacement for std::move.
In this lesson
First decide whether deduction is actually happening
A typical forwarding reference is an unqualified type parameter T&& deduced from this call in a function template. When an lvalue is passed, T can be deduced as U&; when an rvalue is passed, T is deduced as U. const T&& is not a forwarding reference; neither is a member-function parameter T&& whose T is already fixed by a class template.
After deduction, a logical “reference to reference” can appear; the language removes it with reference collapsing: if any layer is an lvalue reference, the result is an lvalue reference; only an rvalue reference combined with an rvalue reference remains an rvalue reference. A wrapper can therefore take lvalues, const lvalues, and rvalues with one declaration.
A named parameter loses the value category of the call expression
Inside the wrapper, the parameter x already has a name, so the expression x is an lvalue. A direct target(x) therefore prefers lvalue overloads for every call. std::forward<T>(x) uses the information stored in T to restore lvalue or rvalue category conditionally, while keeping const and other type properties.
The example defines three overloads for the target, returning markers for a mutable lvalue, a read-only lvalue, and an rvalue. The forwarding wrapper preserves all three choices; a second wrapper that does not forward shows that an rvalue also becomes a named lvalue once it enters the function. This makes the effect of forwarding observable without relying on allocation counts or compiler optimizations.
Perfect forwarding has limits
Forwarding fits factories, in-place construction in containers, and call-adapter layers: that layer does not decide whether to consume the object; it only faithfully passes the caller’s intent. If an interface clearly takes and consumes a known type, an ordinary T&& with std::move is more direct; if the code only needs to read data, const T& may already suffice.
“Perfect” does not mean every syntax can be forwarded transparently. Untyped brace lists, overload sets, and some bit-fields cannot be deduced as ordinary T&&. Forwarding the same argument twice can also let the first call consume a resource while the second sees a moved-from state. Let the interface’s needs decide the scope of forwarding; do not default every parameter to a universal form.
Pitfalls
- Replacing std::forward<T>(x) with std::move(x) also forces the caller’s lvalues to be given up as rvalues, breaking the wrapper’s transparency.
- A T&& whose type is already fixed, and const T&&, are not the forwarding references described here; two ampersands alone are not enough to tell.
Run an example
Minimum C++11 · complete program · Download .cpp
#include <cassert>
#include <iostream>
#include <utility>
int select(int&) { return 1; }
int select(const int&) { return 2; }
int select(int&&) { return 3; }
template<class T>
int relay(T&& x) { return select(std::forward<T>(x)); }
template<class T>
int named(T&& x) { return select(x); }
int main() {
int n = 1;
const int c = 2;
assert(relay(n) == 1);
assert(relay(c) == 2);
assert(relay(3) == 3);
assert(named(3) == 1);
std::cout << relay(n) << ' ' << relay(c) << ' ' << relay(3) << '\n';
}
Compile locally
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread modern-forward.cpp -o example && ./exampleExpected result
1 2 3
CHECK YOUR UNDERSTANDING
Close the answer. Explain it.
When calling relay(n), what are T and the final parameter type? What about relay(std::move(n))?
Show a reference answer
In the first call T is int&, T&& collapses to int&, and the result of forward is an lvalue. In the second, T is int, the parameter type is int&&, and the result of forward is an xvalue. Both calls still refer to the same n; in this example the target only returns a marker and does not actually modify or consume it.
Check the sources
Drafts and official chapters change. The version mark is only the example’s minimum.