Disclaimer: ChatGPT generated document.
The terms forwarding reference and rvalue reference are closely related because they both use && syntax, but they behave differently depending on the context.
| Feature | Rvalue Reference | Forwarding Reference |
|---|---|---|
| Syntax | T&& |
T&& |
| Requires type deduction? | No | Yes |
| Can bind to lvalues? | No | Yes (after deduction) |
| Main purpose | Move semantics | Perfect forwarding |
A normal rvalue reference binds only to rvalues (temporary objects or objects explicitly cast with std::move).
void foo(std::string&& s)
{
std::cout << s << '\n';
}
int main()
{
std::string str = "hello";
foo(std::string("world")); // OK
foo(std::move(str)); // OK
// foo(str); // Error: lvalue
}Here std::string&& is not deduced. It always means "rvalue reference to std::string."
A forwarding reference occurs only when the type is deduced.
Example:
template<typename T>
void foo(T&& x)
{
// x is a forwarding reference
}Now T depends on the argument.
std::string s = "hello";
foo(s);Type deduction gives
T = std::string&So the parameter becomes
std::string& && // reference collapsingwhich collapses to
std::string&So x is actually an lvalue reference.
foo(std::string("hello"));Now
T = std::stringParameter becomes
std::string&&So x is an rvalue reference.
Because it preserves the value category when forwarded.
template<typename T>
void wrapper(T&& arg)
{
target(std::forward<T>(arg));
}If arg was originally
- an lvalue → forwarded as an lvalue
- an rvalue → forwarded as an rvalue
Without std::forward:
target(arg);arg is a named variable, so it is always an lvalue inside the function.
Forwarding references rely on reference collapsing:
& + & -> &
& + && -> &
&& + & -> &
&& + && -> &&Only && && remains an rvalue reference.
void foo(std::string&& x);This is an ordinary rvalue reference.
template<typename T>
struct A
{
void foo(T&& x); // NOT forwarding reference
};Here T is already fixed when an A<T> is instantiated, so there is no deduction during the call.
Example:
A<std::string> a;
std::string s;
a.foo(std::move(s)); // OK
// a.foo(s); // Errortemplate<typename T>
void foo(const T&& x);This is not a forwarding reference because the parameter is not exactly T&&.
A lambda parameter declared as auto&& is also a forwarding reference because auto is deduced.
auto f = [](auto&& x)
{
// forwarding reference
};Ask two questions:
- Is the type being deduced?
- Is the parameter exactly
T&&(orauto&&)?
If both are true, it's a forwarding reference.
Otherwise, it's an rvalue reference.
// Rvalue references
void f(std::string&&);
void f(int&&);
template<typename T>
struct X {
void g(T&&); // rvalue reference
};
// Forwarding references
template<typename T>
void f(T&&);
auto lambda = [](auto&& x) { };
// Not forwarding references
template<typename T>
void f(const T&&);
template<typename T>
void f(std::vector<T>&&);The key distinction is that a forwarding reference is a special kind of && parameter that participates in type deduction and can bind to both lvalues and rvalues, enabling perfect forwarding. An rvalue reference always refers only to rvalues and is primarily used to implement move semantics.
