右值引用匹配(完美转发示例)

rvalue reference matching (perfect forwarding example)

我对下面的完美转发函数感到困惑,其中模板参数 T 可以匹配右值或左值引用:

template<typename T>
void foo(T&& t){
    T::A; // intended error to inspect type
}

int main(){
    std::vector<int> a;
    std::vector<int> && b = std::move(a);

    foo(b);             // T is std::vector<int> & 
    foo(std::move(a));  // T is std::vector<int>
}

我不明白为什么 Tfoo 的模板参数推导在这两种情况下如此不同? t 在函数 foo.

中的基本区别和重要之处是什么

std::move(a) returns 一个右值引用并且 b 已经是一个右值引用(但有一个名称)。

是这样吗,b s 类型是对 std::vector<int> 的右值引用,但据我所知,它有一个名称,因此被认为是函数 main 中的左值?

谁能对此有所启发:-)

&& 与模板一起使用时有一个特殊的类型推导规则。

template <class T>
void func(T&& t) {
}

"When && appears in a type-deducing context, T&& acquires a special meaning. When func is instantiated, T depends on whether the argument passed to func is an lvalue or an rvalue. If it's an lvalue of type U, T is deduced to U&. If it's an rvalue, T is deduced to U:"

func(4);            // 4 is an rvalue: T deduced to int

double d = 3.14;
func(d);            // d is an lvalue; T deduced to double&

float f() {...}
func(f());          // f() is an rvalue; T deduced to float

int bar(int i) {
  func(i);          // i is an lvalue; T deduced to int&
}

此外,参考折叠规则是一本好书。

查看这个以获得非常好的解释:

perfect forwarding

如果您考虑函数的签名,参数的类型是 T&&。在你的第二个例子中,T 被推导为 vector<int>,这意味着你的函数的参数类型是 vector<int>&&。所以你仍然通过(右值)引用。

在另一种情况下,您将 T 推断为 vector<int>&。所以参数的类型是 vector<int> & &&... 或者它会是,但是引用引用是不允许的。引用折叠接管,任何涉及左值引用的双重引用都成为左值引用。所以你传递的是左值引用。

就 b 而言,这是一个众所周知的右值引用问题。本质上,b 的类型是右值引用,但 b 本身仍然具有左值的值类别。这样想:b 本身是一个变量,它必须存在于堆栈中的某个地方,并且有一个地址。所以它是一个左值。这正是在需要转发参数时调用 std::forward 的方式。如果您不这样做,那么它们将始终作为左值参数转发。

我真的很推荐这篇 Scott Meyers 的文章:https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers。仔细阅读!

Is that right that, b s type is a rvalue reference to std::vector<int>, but as far as my understanding goes, it has a name and is thus considered an lvalue in function main?

是的,就是这样。如果您考虑右值引用函数参数,则更有意义:调用者指定该函数可以对其获取的对象做任何它想做的事情。所以在函数体内部,为了确保代码真的可以为所欲为,参数应该被视为左值。同样的论点也可以用于其他右值引用,包括您示例中的 b,尽管程度较小。

表达式 ab都是左值,表达式std::move(a)是右值。

参数 T 的推导使用了特殊的引用折叠规则,因此 t 的类型是左值或右值引用,根据需要绑定到函数调用参数。