IDK
IDK

Reputation: 23

std::forward acting differently

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

Answers (2)

Sam Varshavchik
Sam Varshavchik

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:

  1. T gets deduced as either an lvalue or an rvalue reference, depending on what the parameter is.

  2. 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

Deduplicator
Deduplicator

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

Related Questions