为什么引用折叠规则仅适用于模板?

Why reference collapsing rules work only for templates?

#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?
}

我想我理解了为什么我们在第 2 行有错误。变量 x 是 rvalue reference 到 int 42 并且被认为是一个表达式,x 是一个 左值。在函数 f2 中,输入 r2 是一个 rvalue reference,因此只能绑定到一个 rvalue,所以我们有一个错误。

现在,我的问题是,为什么函数 f1 中看似等效的代码工作得很好?我知道这可能与引用折叠规则有关,即当我们执行 f1(x) 时,我们试图用类型参数 T 为 int && 来实例化 f1,因此输入参数 T&& 为 int&& &&,然后减少到 int &&。换句话说,我们有:

void f1<int &&>(int &&);

这意味着这个实例化与函数 f2 中的完全相同,对吧?那么为什么 f1 有效而 f2 无效?

So why does line 1 works?

模板参数推导中有一个特殊规则被引入以允许完美转发。在模板参数推导的上下文中,T&& 不是 右值引用 ,而是 转发引用

如果将 lvalue 传递给采用 转发引用 的函数模板,则类型参数被推断为 T&而不是 T。这允许 reference collapsing 发生:T& && 变成 T&.

来自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
}

第 2 行没有进行模板参数推导 - f2 采用 右值引用 ,并将拒绝任何不绑定到它的内容。 左值不绑定到右值引用