Reputation: 23
I'm currently learning perfect forwarding in c++ and I came across something that confused me. Pretty sure it's something stupid. When I used std::forward on a lvalue, it used the rvalue function. This is a pretty bad explanation so I'm just gonna show the code.
#include <iostream>
void check(int&& other) {
std::cout << "Rvalue" << std::endl;
}
void check(int& other) {
std::cout << "Lvalue" << std::endl;
}
void call(int& other) {
check(std::forward<int>(other));
}
int main()
{
int i = 4;
call(i);
}
This outputs "Rvalue". Please help me understand why.
Upvotes: 1
Views: 85
Reputation: 118340
"Perfect forwarding" is used in templates, i.e.:
template<typename T>
void func(T && arg)
{
func2(std::forward<T>(arg));
}
Outside of template context the end result drastically changes. All that std::forward
does is return static_cast<T &&>
, so your call
function becomes nothing more than:
void call(int& other) {
check(static_cast<int &&>(other));
}
Hence you get an rvalue. The reason this works differently in templates is because a &&
template parameter is a forwarding reference (a fancy term for either an lvalue or an rvalue-deduced reference, depending on what gets dropped in that parameter), and because of reference collapsing rules. Briefly, when used in a template context, the end result is:
T
gets deduced as either an lvalue or an rvalue reference, depending on what the parameter is.
The result of the static_cast<T &&>
is an lvalue reference, if T
is an lvalue reference, or an rvalue reference if T
is an rvalue reference, due to reference collapsing rules.
The end result is that the same kind of a reference gets forwarded. But this only works in template context, since it requires both forwarding reference semantics and reference collapsing rules to work just right.
Upvotes: 3
Reputation: 45654
std::forward()
isn't exactly magic. Which is one of the reasons one has to give it the appropriate type with the appropriate reference-category (rvalue-reference, lvalue-reference, no reference).
Normally, you get the type from the template-argument. If it's an auto&&
-argument (C++14 lambda, C++20 abbreviated template or use of concept), one uses decltype()
to get it from the function-argument.
Manually specifying it works, but goes against the spirit of using the function. std::move()
or the argument itself is much easier to use in that case, depending on the template-argument you specify.
And due to how reference-collapsing and std::forward()
are defined, Type
and Type&&
both result in an rvalue-reference, while Type&
results in an lvalue-reference.
Upvotes: 1