Templates[temp]
std::forward in C++: perfect forwarding explained
- Section
- Templates [temp]
- Standard
- C++17
- Compilers
- MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2, Clang 23.1 (libc++)
- Published
- Length
- 7 min read
In this answer8 clauses
- 1Why is std::forward needed?
- 2What is a forwarding reference?
- 3std::forward vs std::move: what is the difference?
- 4How do you forward several arguments?
- 5How do you forward in a generic lambda?
- 6Common std::forward mistakes
- 6.1Forwarding the same argument twice
- 6.2A
T&&that is not a forwarding reference - 6.3
std::moveon a forwarding reference - 7Do you still need std::forward in C++23?
- 8When to write std::forward, std::move or neither
Abstract
std::forward<T>(arg) passes a function template's argument on as what it was when it arrived: an lvalue stays an lvalue, an rvalue becomes an rvalue again. Use it on a forwarding reference (a T&& parameter with T deduced, or auto&&), once, where you pass the argument on, because inside the function the named parameter is always an lvalue. Like std::move it is only a cast; any move is made by the function that receives the rvalue and takes it.

std::forward<T>(arg) passes a function template’s argument on to another function as what it was when it arrived: an lvalue (a named variable) stays an lvalue, and an rvalue (such as a temporary) becomes an rvalue again. Use it on a T&& parameter with T deduced, a forwarding reference; that is perfect forwarding. It is in <utility>.
A std::forward example, as a whole program: process is overloaded for lvalues and rvalues, and two wrappers pass their argument on, one without std::forward and one with it.
#include <iostream>
#include <string>
#include <utility>
void process(const std::string&) {
std::cout << "lvalue: copy\n";
}
void process(std::string&&) {
std::cout << "rvalue: move\n";
}
template <typename T>
void without(T&& arg) {
process(arg); // arg has a name: always an lvalue
}
template <typename T>
void with(T&& arg) {
process(std::forward<T>(arg)); // what the caller passed
}
int main() {
std::string name = "Ada";
std::cout << "without std::forward\n";
without(name); // lvalue: copy
without(std::string("Bob")); // lvalue: copy
std::cout << "with std::forward\n";
with(name); // lvalue: copy
with(std::string("Bob")); // rvalue: move
}— end example]
It printed:
without std::forward
lvalue: copy
lvalue: copy
with std::forward
lvalue: copy
rvalue: move— end example]
The samples on this page are C++17, built with MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2 and Clang 23.1 (libc++), and all four printed the same. std::forward itself dates from C++11; Since When lists what each standard added.
Why is std::forward needed?
Because a parameter has a name, and a named variable is an lvalue whatever the caller passed. In without, arg was bound to the temporary "Bob" on the second call, yet process(arg) chose the const std::string& overload both times. std::forward<T>(arg) turns arg back into an rvalue when the caller passed one, so in with the temporary reached process(std::.
Nothing in this program is copied or moved: both overloads take a reference and only print. Their texts name what each is entitled to do with the string: the const std::string& overload can only copy it, and the std::string&& overload may move from it. The two paths through the wrapper, lvalues solid and rvalues dashed:

What is a forwarding reference?
A forwarding reference is a parameter declared T&& where T is a template parameter of that function, deduced from the argument; auto&& is one too. It binds to lvalues and rvalues alike and records which one it got in T (temp.deduct.call). Older writing calls it a universal reference. With a std::string argument:
| Argument | T is |
The parameter T&& is |
|---|---|---|
| An lvalue | std::string& |
std::string& |
| A const lvalue | const std::string& |
const std::string& |
| An rvalue | std::string |
std::string&& |
The first two rows rest on reference collapsing (dcl.ref): a reference to a reference is & unless both are &&, so & &, & && and && & give &, and only && && gives &&. The const travels in T, so a const argument stays const. std::forward<T> reads that T back: the standard defines its result as static_cast<T&&>(t) (forward), an lvalue when T is std::string& and an rvalue when it is std::string.
std::forward vs std::move: what is the difference?
std::move(x) always makes an rvalue; std::forward<T>(x) makes one only when the caller passed an rvalue.
| Question | std::move(x) |
std::forward<T>(x) |
|---|---|---|
| What it returns | an rvalue, always | an rvalue if the caller passed one, else an lvalue |
| Use it on | an object you own and are done with | a forwarding reference, once, where you pass it on |
| The type | deduced from x |
written out: <T> |
Neither moves anything: both are casts, and the move happens in the function that receives the rvalue and takes it, as move semantics and std::move explains. The <T> is not optional: std::forward(arg) did not compile on any of the four, because its parameter, remove_reference_t<T>&, gives the compiler nothing to deduce T from.
How do you forward several arguments?
Take a parameter pack of forwarding references, Args&&... args, and expand std::: one std::forward per argument, each with its own type. Here make builds a Widget from a name and a size, through one constructor that copies the name or one that moves it:
struct Widget {
std::string name;
int size;
Widget(const std::string& n, int s) : name(n), size(s) {
std::cout << "Widget: copied the name\n";
}
Widget(std::string&& n, int s) : name(std::move(n)), size(s) {
std::cout << "Widget: moved the name\n";
}
};
template <typename T, typename... Args>
T make(Args&&... args) {
return T(std::forward<Args>(args)...);
}
int main() {
std::string label = "knob";
Widget a = make<Widget>(label, 3); // copied
Widget b = make<Widget>(std::string("dial"), 5); // moved
std::cout << a.name << ' ' << a.size << ", "
<< b.name << ' ' << b.size << ", label: " << label
<< '\n';
}— end example]
It printed:
Widget: copied the name
Widget: moved the name
knob 3, dial 5, label: knob— end example]
The named label went to the copying constructor and still held knob afterwards; the temporary "dial" went to the moving one, and the int travelled beside the name both times. std::make_unique and emplace_back pass constructor arguments through the same way. Parameter packs and fold expressions are in the templates guide.
How do you forward in a generic lambda?
A generic lambda’s auto&& parameter is a forwarding reference too, but there is no T to name, so write std::. decltype(x) is std::string& for an lvalue and std::string&& for an rvalue (a deduced T would be plain std::string); std:: returns std::string&& &&, which collapses to std::string&&, so either one is read correctly:
auto call = [](auto&& x) {
process(std::forward<decltype(x)>(x));
};
std::string name = "Ada";
call(name); // lvalue: copy
call(std::string("Bob")); // rvalue: move— end example]
It printed lvalue: copy, then rvalue: move, as with did. Generic lambdas and function objects covers the pack form, auto&&... args.
Common std::forward mistakes
Two of these three compile without a warning at /W4 or -Wall -Wextra.
Forwarding the same argument twice
Forward an argument once, at its last use: the first std::forward may hand it to a function that moves from it, and the second call gets what is left. Here log_name and save_name both take a std::string by value:
template <typename T>
void record(T&& name) {
log_name(std::forward<T>(name)); // may move out of name
save_name(std::forward<T>(name)); // may see a moved-from string
}— end example]
Given a temporary, save_name got 0 characters on all three standard libraries; given an lvalue, both functions got a copy. The standard leaves a moved-from string unspecified, so it may be empty, and was here. clang-tidy’s bugprone-use-after-move check reports the second call: 'name' used after it was forwarded.
A T&& that is not a forwarding reference
T&& is a forwarding reference only when T is deduced at that call. In Box<T>, a class template, the member put(T&& x) uses the class’s T, so Box<std:: takes rvalues only, and an lvalue std::string s does not bind:
b.put(s); // error: s is an lvalue— end example]
Clang’s error:
error: rvalue reference to type 'basic_string<...>' cannot bind to lvalue of type 'basic_string<...>'— end example]
const T&& is not a forwarding reference either. A member that must take lvalues too needs a template parameter of its own, template <typename U> void put(U&& x), and passes std::forward<U>(x) on.
std::move on a forwarding reference
std::move(arg) in a wrapper passes even the caller’s named variable on as an rvalue, so the function it reaches may move from it. When that function took its parameter by value, as keep(std:: does, the caller’s name was empty afterwards on all four builds; with process, which only takes a reference, it still held "Ada".
Do you still need std::forward in C++23?
Yes: for a parameter of a function template, std::forward is still the tool. C++23 adds std::forward_like, which forwards a member the way the object holding it was passed, most often with an explicit object parameter (this auto&& self, “deducing this”, also C++23); all four compilers built the pair as C++23.
When to write std::forward, std::move or neither
Which one depends on what you hold:
| You hold | Write |
|---|---|
A T&& parameter, T deduced, passed on |
std::forward<T>(arg), once, at its last use |
A pack Args&&... args |
std:: |
A generic lambda’s auto&& x |
std:: |
A member, through this auto&& self |
std::, C++23 |
| An object you own and are done with | std::move(x) |
| An argument you only read | neither: take it by const& or by value |
Functions that use their arguments rather than pass them on need no forwarding: whether to pass by value or by const reference settles theirs. The Templates section has the other answers on templates.
Questions
What does std::forward do?
std::forward<T>(arg) passes a function template's argument on to another function as what it was when it arrived: an lvalue stays an lvalue and an rvalue becomes an rvalue again. It is used on a forwarding reference, a T&& parameter whose T the call deduced.
What is perfect forwarding?
Passing arguments through a function template to another function with their value category and constness unchanged: an lvalue arrives as an lvalue, an rvalue as an rvalue, a const argument as const. In C++ it is written as a forwarding reference parameter (T&& with T deduced, or auto&&) passed on with std::forward.
When should I use std::forward instead of std::move?
On a forwarding reference, once, at the point where you pass the argument on. Use std::move on an object you own and are done with. std::move on a forwarding reference passes the caller’s named variable on as an rvalue too, so a function that takes it by value can leave the caller’s variable empty.
Which header is std::forward in?
The <utility> header. std::forward has been in the standard library since C++11, and constexpr since C++14.
What is a forwarding reference?
A function parameter declared T&&, where T is a template parameter of that function deduced from the argument, or a parameter declared auto&&. It binds to lvalues and rvalues alike and records which one it got in T. T&& in a member of a class template, whose T belongs to the class, and const T&& are rvalue references instead. Older writing calls it a universal reference.
How is std::forward implemented?
Its implementation is a cast and nothing more. The standard gives it two overloads, template<class T> constexpr T&& forward(remove_reference_t<T>& t) noexcept and the same taking remove_reference_t<T>&&; both return static_cast<T&&>(t), and the second refuses to compile when T is an lvalue reference. It moves nothing: any move is made by the function that receives the rvalue and takes it, such as a move constructor.
Sources
- std::forwardcppreference.com
- Reference declarationcppreference.com
- std::forward_likecppreference.com
- [forward] Forward/move helpersC++ standard working draft
- [temp.deduct.call] Deducing template arguments from a function callC++ standard working draft
- [dcl.ref] ReferencesC++ standard working draft
- <utility> functionsMicrosoft Learn