#include <iostream>
template <typename T> void f1(T&& r1){
    std::cout<<r1;
}
void f2(int&& r2){
    std::cout<<r2;
}
int main() {
    int&& x = 42;
    f1(x); //line 1: No error here. Why?
    f2(x);//line2: Error here. why?
}
I think that I understand why we have an error on line 2. The variable x is rvalue reference to int 42 and being considered as an expression, x is a lvalue. In function f2, the input r2 is a rvalue reference and thus can only bind to a rvalue, so we have an error.
Now, my question is, why the seemingly equivalent code in function f1 works just fine? I know this might have something to do with the reference collapsing rules, i.e., when we execute f1(x), we are trying to instantiate f1 with type parameter T being int &&, so the input parameter T&& is int&& &&, which then reduces to int &&. In other words, we have:
void f1<int &&>(int &&);
which means this instantiation is exactly the same as in function f2, right? So why f1 works and f2 does not?
So why does line 1 works?
There is a special rule in template argument deduction that was introduced to permit perfect-forwarding. In the context of template argument deduction, T&& is not an rvalue reference but a forwarding reference instead.
If an lvalue is passed to a function template taking a forwarding reference, the type parameter is deduced as T& instead of T. This allows reference collapsing to take place: T& && becomes T&.
From cppreference:
If P is an rvalue reference to a cv-unqualified template parameter (so-called forwarding reference), and the corresponding function call argument is an lvalue, the type lvalue reference to A is used in place of A for deduction (Note: this is the basis for the action of std::forward Note: in class template argument deduction, template parameter of a class template is never a forwarding reference (since C++17))
template<class T> int f(T&&); // P is an rvalue reference to cv-unqualified T (forwarding reference) template<class T> int g(const T&&); // P is an rvalue reference to cv-qualified T (not special) int main() { int i; int n1 = f(i); // argument is lvalue: calls f<int&>(int&) (special case) int n2 = f(0); // argument is not lvalue: calls f<int>(int&&) // int n3 = g(i); // error: deduces to g<int>(const int&&), which // cannot bind an rvalue reference to an lvalue }
In line 2 there is no template argument deduction going on - f2 takes an rvalue reference, and will reject anything that will not bind to that. Lvalues do not bind to rvalue references.
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