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.
The call consume(x)
will always select the const
lvalue reference overload of consume()
because the expression x
is an lvalue. This is regardless of the deduced type for x
.
However, by calling consume()
instead as:
consume(std::forward<T>(x));
It will select the rvalue reference overload if the value category of the argument passed to foo()
(i.e., the wrapper function template with forwarding references) was an rvalue.
Roughly speaking, std::forward
propagates or preserves the original value category of foo()
's argument to the argument of the nested call consume()
:
template <typename T>
void foo(T&& x) {
// preserve original value category of foo()'s argument
consume(std::forward<T>(x));
}
That is, if foo()
is passed an rvalue, e.g.:
foo(1); // selects consume(int &&)
This argument's value category – 1
, an rvalue – is further propagated through std::forward
to the call to consume()
, and therefore the rvalue reference overload (i.e., void consume(int &&)
) is selected. If foo()
is passed an lvalue instead, e.g.:
foo(i); // selects consume(const int&)
Since i
is an lvalue, and std::forward
preserves the original value category of this argument to the call to consume()
, the lvalue reference overload is selected – i.e., void consume(const int&)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With