寻求对std::forward的澄清
Seeking clarification on std::forward
指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 值。
- 完美前进使用 std::forward vs RefRefCast
- 你如何理解"std: :forward is just syntactic sugar"?这是真的吗?
- "std::forward"和"std::move"真的不生成代码吗?
- 在C++中使用 std::forward 的多个参数
- 如何使用 std::forward 精确地评估参数包的扩展?
- 普通的右值引用和 std::forward 返回的引用有什么区别?
- 怎么可能写 f( *this, std::forward<Args>(args)... ) 而 f 只用 F f 声明;
- 如何通过通用引用或std::forward将这三个c++模板函数合并为一个
- 自 C++11 年以来对 std::forward 实施的理解
- 当将参数包传递给另一个可变参数模板函数时,我是否必须使用 std::forward
- 为什么要在概念中使用std::forward
- 寻求对std::forward的澄清
- 为什么 std::forward 将左值和右值转换为右值引用?
- std::forward() 的右值引用重载的目的是什么?
- std::forward 如何推断出"_Ty"的类型?
- 没有匹配函数调用 std::forward(const std::string &) 与可变参数
- C++ decltype(auto) or decltype(std::<T>forward(value))?
- std::function operator() 和 std::forward 中发生了什么?
- How to std::forward( *this )
- 在哪些情况下,从 std::forward 分配比从 std::move 分配更可取?为什么