寻求对std::forward的澄清

Seeking clarification on std::forward

本文关键字:forward std      更新时间:2023-10-16

指std::forward

对于所有重载,返回类型已指定为T&&(忽略constexpr)。

但在以下示例所附的说明中:

template<class T>
void wrapper(T&& arg) 
{
// arg is always lvalue
foo(std::forward<T>(arg)); // Forward as lvalue or as rvalue, depending on T
}
  • 如果对 wrapper() 的调用传递了一个 rvalue std::string,则 T 是 推导为 std::string(不是 std::string&, const std::string&, or std::string&&),和 std::forward 确保右值引用是 传给福。

  • 如果对 wrapper() 的调用传递了一个 const lvalue std::string,则 T 被推导出为 const std::string&,并且 std::forward 确保将常量左值引用传递给 FOO。

  • 如果对 wrapper() 的调用传递了一个非常量左值 std::string,则 T 推导为 std::string&,并且 std::forward 确保非常量 左值引用传递给 FOO。

在上面两个实例中,第一个实例之后,左值引用而不是右值引用(如T&&所暗示的那样,这种理解是否正确?)已被记录为传递给foo。

如果上述理解是正确的,为什么返回值被指定为T&&

两者之间有区别

void f1(int&& a) {}
template<class T>
void f2(T&& a) {}

第一个版本是定义处理右值的f1。另一方面,第二个版本是一个模板函数,它接受通用(或在某些引用中,转发)引用作为其参数。

要理解std::forward的机制,你应该用不同的参数调用f2,如下所示:

#include <iostream>
template <class T> void f2(T &&a) { std::cout << __PRETTY_FUNCTION__ << 'n'; }
int main() {
int a{5};
f2(5);
f2(a);
return 0;
}

编译代码时,例如使用g++,您可以从程序获得以下输出:

./a.out
void f2(T &&) [T = int]
void f2(T &&) [T = int &]

如您所见,在第一次调用中,T被推导为int,而在第二次调用中,它被推导为int &。由于参考折叠规则,正如在您的问题评论中已经提到的,T &&会给你T,而T& &&会给你T&。简而言之,将T&&作为返回类型观察并不意味着函数返回右值引用。实际上,在模板函数中,&&就像一个标识运算符。当与T&结合使用时,它会给你T&;否则,它会给你T.

Arthur O'Dwyer在CppCon2016上有一个非常好的演讲。也许你可以看看它以了解模板类型推断规则,这将有助于您澄清std::forward行为。

我想我会尝试以尽可能简单的方式解释一般情况(不遗漏任何细节),因为我对std::forward感到困惑的时间最长。

让我们[base-type]引用参数类型的未引用部分(它可以有其他类型描述符,如const但没有&)。这些都是免费(即未确定)模板参数的官方参考折叠规则。最左边的列表示T的类型。 最右边的列表示相应的T&T&&类型(即模板中的arg类型)。

Type of T              Type of arg
.     |                       |
.     v                       v
1 [base-type]   + &   => [base-type]&
2 [base-type]   + &&  => [base-type]&&
3 [base-type]&  + &   => [base-type]&
4 [base-type]&  + &&  => [base-type]&
5 [base-type]&& + &   => [base-type]&
6 [base-type]&& + &&  => [base-type]&&

arg被指定为类型T&&时,只有规则 2、4 或 6 适用于类型扣除。

如果传递的arg具有[base-type]&&形式,则仅规则 2 和 6 适用。 如果使用规则 2,则[base-type]将替换为T。 如果使用规则 6[base-type]&&将替换为T。 这些情况实际上是相同的,因为返回类型T&&将在这两种情况下[base-type]&&

Rule 2: T + && = [base-type] + && = [base-type]&&
Rule 6: T + && = [base-type]&& + && = [base-type]&&

从我所做的测试中,编译器总是选择T=[base-type]而不是[base-type]&[base-type]&&,只要前者是有效的替换。因此,当形式[base-type]&&的参数(arg)传递给模板函数时,T总是被推导为[base-type]

如果传递的arg具有[base-type]&形式,则仅规则 4 适用,并且必须用规则[base-type]&代替T。 这次只有一种可能。 因此,返回类型为...

Rule 4: T + && = [base-type]& + && = [base-type]&

底线是这样的:

在每种情况下,从std::forward<T>(arg)返回的类型都是T&&的折叠版本,与arg.

请注意,这仅在arg被指定为类型T&&时才有效。 将arg指定为T&T不会生成所需的通用转发,因此您永远不应该这样做。arg T&&是唯一的通用转发参数规范。

我写了一个可以在这里在线运行的小型教程程序。 它演示T&&可以表示对 l 值和 r 值的引用。 它还表明T&T不起作用。

使用通用转发传递 L 值和 R 值。