转发引用不是推导为 r 值引用吗?

Aren't forwarding references deduced as r-value references?

我有一个关于转发参考的具体问题。 (我认为)我理解 r 值引用和 std::move,但我很难理解转发引用:

#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);
}

Tint,没关系。如果 xint&& 类型,为什么它打印“左值”而我们需要 std::forward ?我的意思是,从 int&&const int& 的转换在哪里?

调用 consume(x) 总是 select consume()const 左值引用重载 因为表达式 x 是一个 左值 。这与 x.

的推导类型无关

但是,通过调用 consume() 而不是:

consume(std::forward<T>(x));

它将select右值引用重载如果参数的值类别传递给foo()(即具有转发引用的包装函数模板) 是一个 rvalue.


粗略地说,std::forwardfoo()的参数的原始值类别传播或保留到嵌套调用的参数consume():

template <typename T>
void foo(T&& x) {
   // preserve original value category of foo()'s argument
   consume(std::forward<T>(x));
}

也就是说,如果 foo() 传递了一个右值,例如:

foo(1); // selects consume(int &&)

这个参数的值类别 - 1,一个右值 - 通过 std::forward 进一步传播到对 consume() 的调用,因此右值引用重载(即 void consume(int &&)) 是 selected。如果 foo() 传递了一个左值,例如:

foo(i); // selects consume(const int&)

因为 i 是一个左值,并且 std::forward 保留了这个参数的原始值类别到对 consume() 的调用,所以左值引用重载是 selected – 即 void consume(const int&).

类型和value categories是表达式的两个独立属性。

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.

x的类型是int&&,但是x是变量名,x是左值表达式本身,就是不能绑定到 int&&(但可以绑定到 const int&)。

(强调我的)

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 as std::cin or std::endl. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression;

这意味着当函数同时具有左值引用和 rvalue-reference overloads, value categories are considered in overload resolution 时。

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用于右值或左值的转换,与转发引用参数的原始值类别一致。当左值int传递给foo时,T被推导为int&,那么std::forward<T>(x)将是一个左值表达式;当右值 int 传递给 foo 时,T 被推导为 int,那么 std::forward<T>(x) 将是一个右值表达式。因此 std::forward 可用于保留原始转发引用参数的值类别。作为对比 std::move 总是将参数转换为右值表达式。