std::forward
std::forward
Prerequisites
1. What is std::forward?
std::forward is a utility used to preserve the value category of an argument.
That means it keeps whether an argument is:
- an lvalue
- or an rvalue
1
#include <utility>
It is mainly used in template code with forwarding references.
Use it when:
- you are inside a template
- parameter is a forwarding reference:
T&& - you want to pass the argument onward while preserving its original category
2. Why Do We Need It?
Consider this function:
1
2
3
4
5
6
7
8
9
void process(int& x)
{
std::cout << "lvalue\n";
}
void process(int&& x)
{
std::cout << "rvalue\n";
}
Now:
1
2
3
4
5
template<typename T>
void wrapper(T&& arg)
{
process(arg);
}
At first glance, it looks fine.
But actually:
1
2
3
int x = 10;
wrapper(x); // lvalue
wrapper(10); // rvalue?
Problem:
Inside wrapper, arg itself is always a named variable. A named variable is always treated as an lvalue
So this:
1
process(arg);
will always call:
1
process(int&)
even if the original argument was an rvalue.
The Solution: std::forward
1
2
3
4
5
template<typename T>
void wrapper(T&& arg)
{
process(std::forward<T>(arg));
}
Now:
1
2
3
int x = 10;
wrapper(x); // lvalue
wrapper(10); // rvalue
Correct overload is preserved
std::forward<T>(arg) conditionally casts arg to:
T&if original argument was an lvalueT&&if original argument was an rvalue
In other words, it forwards the argument exactly as it was received
Forwarding Reference
std::forward is usually used with this pattern:
1
2
template<typename T>
void func(T&& arg)
This T&& is called a:
- forwarding reference
- sometimes called universal reference
This only works in template type deduction contexts.
Example Without std::forward
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <utility>
void process(int& x)
{
std::cout << "lvalue\n";
}
void process(int&& x)
{
std::cout << "rvalue\n";
}
template<typename T>
void wrapper(T&& arg)
{
process(arg);
}
int main()
{
int x = 10;
wrapper(x); // lvalue
wrapper(20); // still lvalue ❌
}
The second call is wrong because arg is a named variable.
Example With std::forward
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <utility>
void process(int& x)
{
std::cout << "lvalue\n";
}
void process(int&& x)
{
std::cout << "rvalue\n";
}
template<typename T>
void wrapper(T&& arg)
{
process(std::forward<T>(arg));
}
int main()
{
int x = 10;
wrapper(x); // lvalue
wrapper(20); // rvalue ✅
}
std::move vs std::forward
This is the most important comparison.
✔️ std::move
1
std::move(x)
Always casts to rvalue
✔️ std::forward
1
std::forward<T>(x)
Conditionally preserves lvalue/rvalue nature
3. Common Mistakes
❌ Using std::forward outside forwarding context
1
2
int x = 10;
std::forward<int>(x); // meaningless / dangerous
std::forward is mainly for template forwarding references
❌ Using std::move instead of std::forward
1
2
3
4
5
template<typename T>
void wrapper(T&& arg)
{
process(std::move(arg)); // ❌ forces rvalue
}
Problem:
- lvalues are also converted into rvalues
- may accidentally move from objects you should not move from
❌ Forgetting that named rvalue references are lvalues
1
2
int&& x = 10;
process(x); // calls lvalue overload
Even though x is declared as int&&, the variable x itself is an lvalue because it has a name.
4. Performance Perspective
std::forward itself is extremely cheap. It is basically a cast resolved at compile time.
Performance benefits:
- avoids unnecessary copies
- preserves move opportunities
- enables efficient generic code
Common use cases:
- wrapper functions
- factory functions
- emplace-style APIs
- generic libraries
Without std::forward, rvalues may degrade into lvalues, causing:
- extra copies
- loss of move semantics
- worse performance