I have a specific question regarding forwarding references. (I think) I understand r-value references and std::move
, but I have trouble understanding forwarding references:
#include <iostream>
#include <utility>
template <typename T> class TD; // from "Effective Modern C++"
void consume(const int &i) { std::cout << "lvalue\n"; }
void consume(int &&i) { std::cout << "rvalue\n"; }
template <typename T>
void foo(T&& x) {
// TD<decltype(x)> xType; - prints int&&
consume(x);
}
int main() {
foo(1 + 2);
}
T
is int
, that's fine. If x
is of type int&&
, why it prints "lvalue" and we need std::forward
? I mean, where is the conversion from int&&
to const int&
here?
Types and value categories are two independent properties of expression.
Each C++ expression (an operator with its operands, a literal, a variable name, etc.) is characterized by two independent properties: a type and a value category. Each expression has some non-reference type, and each expression belongs to exactly one of the three primary value categories: prvalue, xvalue, and lvalue.
The type of x
is int&&
, but x
is the name of variable and x
is an lvalue expression itself, which just can't be bound to int&&
(but could be bound to const int&
).
(emphasis mine)
The following expressions are lvalue expressions:
- the name of a variable, a function
, a template parameter object (since C++20)
, or a data member, regardless of type, such asstd::cin
orstd::endl
. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression;
That means when function has both lvalue-reference and rvalue-reference overloads, value categories are considered in overload resolution too.
More importantly, when a function has both rvalue reference and lvalue reference overloads, the rvalue reference overload binds to rvalues (including both prvalues and xvalues), while the lvalue reference overload binds to lvalues:
If any parameter has reference type, reference binding is accounted for at this step: if an rvalue argument corresponds to non-const lvalue reference parameter or an lvalue argument corresponds to rvalue reference parameter, the function is not viable.
std::forward
is used for the conversion to rvalue or lvalue, consistent with the original value category of forwarding reference argument. When lvalue int
is passed to foo
, T
is deduced as int&
, then std::forward<T>(x)
will be an lvalue expression; when rvalue int
is passed to foo
, T
is deduced as int
, then std::forward<T>(x)
will be an rvalue expression. So std::forward
could be used to reserve the value category of the original forwarding reference argument. As a contrast std::move
always converts parameter to rvalue expression.