为什么转发引用需要 std::forward
Why is std::forward necessary with forwarding references
在这样的函数模板中
template <typename T>
void foo(T&& x) {
bar(std::forward<T>(x));
}
如果foo
是用右值引用调用的,x
不是foo
中的右值引用吗?如果使用左值引用调用 foo,则无论如何都不需要强制转换,因为 x
也将是 foo
内的左值引用。此外 T
将被推导为左值引用类型,因此 std::forward<T>
不会更改 x
.
的类型
我使用 boost::typeindex
进行了测试,使用和不使用 std::forward<T>
我得到的类型完全相同。
#include <iostream>
#include <utility>
#include <boost/type_index.hpp>
using std::cout;
using std::endl;
template <typename T> struct __ { };
template <typename T> struct prt_type { };
template <typename T>
std::ostream& operator<<(std::ostream& os, prt_type<T>) {
os << "3[1;35m" << boost::typeindex::type_id<T>().pretty_name()
<< "3[0m";
return os;
}
template <typename T>
void foo(T&& x) {
cout << prt_type<__<T>>{} << endl;
cout << prt_type<__<decltype(x)>>{} << endl;
cout << prt_type<__<decltype(std::forward<T>(x))>>{} << endl;
cout << endl;
}
int main(int argc, char* argv[])
{
foo(1);
int i = 2;
foo (i);
const int j = 3;
foo(j);
foo(std::move(i));
return 0;
}
g++ -Wall test.cc && ./a.out
与 gcc 6.2.0
和 boost 1.62.0
的输出是
__<int>
__<int&&>
__<int&&>
__<int&>
__<int&>
__<int&>
__<int const&>
__<int const&>
__<int const&>
__<int>
__<int&&>
__<int&&>
编辑:我找到了这个答案: 显然,
as soon as you give a name to the parameter it is an lvalue.
我的问题是,为什么选择这种行为而不是将右值引用保留为右值,即使它们被赋予了名称?在我看来,可以通过这种方式规避整个转发考验。
Edit2:我不是在问 std::forward
做什么。我在问 为什么 需要它。
x 不是 foo 中的右值引用吗?
不,x
是 foo
中的一个左值(它有一个名称和一个地址)类型的右值引用。将其与引用折叠规则和模板类型推导规则相结合,您会发现您需要 std::forward
才能获得正确的引用类型。
基本上,如果您作为 x
传递给的是一个左值,比如 int
,那么 T
会推导为 int&
。然后 int && &
变为 int&
(由于引用折叠规则),即左值引用。
另一方面,如果你传递一个右值,比如 42
,那么 T
被推导为 int
,所以最后你有一个 int&&
作为 x
的类型,即右值。基本上这就是 std::forward
所做的:转换为 T&&
结果,就像
static_cast<T&&>(x)
由于参考折叠规则,它变成 T&&
或 T&
。
它的用处在泛型代码中变得很明显,在泛型代码中您可能事先不知道您将获得右值还是左值。如果你不调用 std::forward
而只调用 f(x)
,那么 x
将 始终是左值 ,因此你将失去移动语义需要时,最终可能会得到 un-necessary 份等
您可以看到其中差异的简单示例:
#include <iostream>
struct X
{
X() = default;
X(X&&) {std::cout << "Moving...\n";};
X(const X&) {std::cout << "Copying...\n";}
};
template <typename T>
void f1(T&& x)
{
g(std::forward<T>(x));
}
template <typename T>
void f2(T&& x)
{
g(x);
}
template <typename T>
void g(T x)
{ }
int main()
{
X x;
std::cout << "with std::forward\n";
f1(X{}); // moving
std::cout << "without std::forward\n";
f2(X{}); // copying
}
I get exactly the same types with and without std::forward<T>
...不是吗?你自己的输出证明你错了:
__<int> // T
__<int&&> // decltype(x)
__<int&&> // std::forward<T>(x)
如果不使用 std::forward<T>
或 decltype(x)
,您将得到 int
而不是 int&&
。这可能会无意中失败 "propagate the rvalueness" of x
- 考虑这个例子:
void foo(int&) { cout << "int&\n"; }
void foo(int&&) { cout << "int&&\n"; }
template <typename T>
void without_forward(T&& x)
{
foo(x);
// ^
// `x` is an lvalue!
}
template <typename T>
void with_forward(T&& x)
{
// `std::forward` casts `x` to `int&&`.
// vvvvvvvvvvvvvvvvvv
foo(std::forward<T>(x));
// ^
// `x` is an lvalue!
}
template <typename T>
void with_decltype_cast(T&& x)
{
// `decltype(x)` is `int&&`. `x` is casted to `int&&`.
// vvvvvvvvvvv
foo(decltype(x)(x));
// ^
// `x` is an lvalue!
}
int main()
{
without_forward(1); // prints "int&"
with_forward(1); // prints "int&&"
with_decltype_cast(1); // prints "int&&"
}
x
是 r-value 与 x
具有 r-value 引用类型不同。
R-value 是 表达式 的 属性,而 r-value-reference 是其 [=] 的 属性 32=]类型.
如果您实际上尝试传递一个作为函数引用的 r-value 变量,它会被视为 l-value。 decltype
误导了你。 Try it and see:
#include <iostream>
#include <typeinfo>
using namespace std;
template<class T> struct wrap { };
template<class T>
void bar(T &&value) { std::cout << " vs. " << typeid(wrap<T>).name() << std::endl; }
template<class T>
void foo(T &&value) { std::cout << typeid(wrap<T>).name(); return bar(value); }
int main()
{
int i = 1;
foo(static_cast<int &>(i));
foo(static_cast<int const &>(i));
foo(static_cast<int &&>(i));
foo(static_cast<int const &&>(i));
}
输出:
4wrapIRiE
vs. 4wrapIRiE
4wrapIRKiE
vs. 4wrapIRKiE
4wrapIiE
vs. 4wrapIRiE
(these should match!)
4wrapIKiE
vs. 4wrapIRKiE
(these should match!)
您真的不希望您的参数自动移动到调用的函数中。考虑这个函数:
template <typename T>
void foo(T&& x) {
bar(x);
baz(x);
global::y = std::forward<T>(x);
}
现在您真的不 想要自动移动到 bar
和一个空参数到 baz
。
当前要求您指定是否以及何时移动或转发参数的规则并非偶然。
在这样的函数模板中
template <typename T>
void foo(T&& x) {
bar(std::forward<T>(x));
}
如果foo
是用右值引用调用的,x
不是foo
中的右值引用吗?如果使用左值引用调用 foo,则无论如何都不需要强制转换,因为 x
也将是 foo
内的左值引用。此外 T
将被推导为左值引用类型,因此 std::forward<T>
不会更改 x
.
我使用 boost::typeindex
进行了测试,使用和不使用 std::forward<T>
我得到的类型完全相同。
#include <iostream>
#include <utility>
#include <boost/type_index.hpp>
using std::cout;
using std::endl;
template <typename T> struct __ { };
template <typename T> struct prt_type { };
template <typename T>
std::ostream& operator<<(std::ostream& os, prt_type<T>) {
os << "3[1;35m" << boost::typeindex::type_id<T>().pretty_name()
<< "3[0m";
return os;
}
template <typename T>
void foo(T&& x) {
cout << prt_type<__<T>>{} << endl;
cout << prt_type<__<decltype(x)>>{} << endl;
cout << prt_type<__<decltype(std::forward<T>(x))>>{} << endl;
cout << endl;
}
int main(int argc, char* argv[])
{
foo(1);
int i = 2;
foo (i);
const int j = 3;
foo(j);
foo(std::move(i));
return 0;
}
g++ -Wall test.cc && ./a.out
与 gcc 6.2.0
和 boost 1.62.0
的输出是
__<int>
__<int&&>
__<int&&>
__<int&>
__<int&>
__<int&>
__<int const&>
__<int const&>
__<int const&>
__<int>
__<int&&>
__<int&&>
编辑:我找到了这个答案: 显然,
as soon as you give a name to the parameter it is an lvalue.
我的问题是,为什么选择这种行为而不是将右值引用保留为右值,即使它们被赋予了名称?在我看来,可以通过这种方式规避整个转发考验。
Edit2:我不是在问 std::forward
做什么。我在问 为什么 需要它。
x 不是 foo 中的右值引用吗?
不,x
是 foo
中的一个左值(它有一个名称和一个地址)类型的右值引用。将其与引用折叠规则和模板类型推导规则相结合,您会发现您需要 std::forward
才能获得正确的引用类型。
基本上,如果您作为 x
传递给的是一个左值,比如 int
,那么 T
会推导为 int&
。然后 int && &
变为 int&
(由于引用折叠规则),即左值引用。
另一方面,如果你传递一个右值,比如 42
,那么 T
被推导为 int
,所以最后你有一个 int&&
作为 x
的类型,即右值。基本上这就是 std::forward
所做的:转换为 T&&
结果,就像
static_cast<T&&>(x)
由于参考折叠规则,它变成 T&&
或 T&
。
它的用处在泛型代码中变得很明显,在泛型代码中您可能事先不知道您将获得右值还是左值。如果你不调用 std::forward
而只调用 f(x)
,那么 x
将 始终是左值 ,因此你将失去移动语义需要时,最终可能会得到 un-necessary 份等
您可以看到其中差异的简单示例:
#include <iostream>
struct X
{
X() = default;
X(X&&) {std::cout << "Moving...\n";};
X(const X&) {std::cout << "Copying...\n";}
};
template <typename T>
void f1(T&& x)
{
g(std::forward<T>(x));
}
template <typename T>
void f2(T&& x)
{
g(x);
}
template <typename T>
void g(T x)
{ }
int main()
{
X x;
std::cout << "with std::forward\n";
f1(X{}); // moving
std::cout << "without std::forward\n";
f2(X{}); // copying
}
I get exactly the same types with and without
std::forward<T>
...不是吗?你自己的输出证明你错了:
__<int> // T
__<int&&> // decltype(x)
__<int&&> // std::forward<T>(x)
如果不使用 std::forward<T>
或 decltype(x)
,您将得到 int
而不是 int&&
。这可能会无意中失败 "propagate the rvalueness" of x
- 考虑这个例子:
void foo(int&) { cout << "int&\n"; }
void foo(int&&) { cout << "int&&\n"; }
template <typename T>
void without_forward(T&& x)
{
foo(x);
// ^
// `x` is an lvalue!
}
template <typename T>
void with_forward(T&& x)
{
// `std::forward` casts `x` to `int&&`.
// vvvvvvvvvvvvvvvvvv
foo(std::forward<T>(x));
// ^
// `x` is an lvalue!
}
template <typename T>
void with_decltype_cast(T&& x)
{
// `decltype(x)` is `int&&`. `x` is casted to `int&&`.
// vvvvvvvvvvv
foo(decltype(x)(x));
// ^
// `x` is an lvalue!
}
int main()
{
without_forward(1); // prints "int&"
with_forward(1); // prints "int&&"
with_decltype_cast(1); // prints "int&&"
}
x
是 r-value 与 x
具有 r-value 引用类型不同。
R-value 是 表达式 的 属性,而 r-value-reference 是其 [=] 的 属性 32=]类型.
如果您实际上尝试传递一个作为函数引用的 r-value 变量,它会被视为 l-value。 decltype
误导了你。 Try it and see:
#include <iostream>
#include <typeinfo>
using namespace std;
template<class T> struct wrap { };
template<class T>
void bar(T &&value) { std::cout << " vs. " << typeid(wrap<T>).name() << std::endl; }
template<class T>
void foo(T &&value) { std::cout << typeid(wrap<T>).name(); return bar(value); }
int main()
{
int i = 1;
foo(static_cast<int &>(i));
foo(static_cast<int const &>(i));
foo(static_cast<int &&>(i));
foo(static_cast<int const &&>(i));
}
输出:
4wrapIRiE
vs.4wrapIRiE
4wrapIRKiE
vs.4wrapIRKiE
4wrapIiE
vs.4wrapIRiE
(these should match!)
4wrapIKiE
vs.4wrapIRKiE
(these should match!)
您真的不希望您的参数自动移动到调用的函数中。考虑这个函数:
template <typename T>
void foo(T&& x) {
bar(x);
baz(x);
global::y = std::forward<T>(x);
}
现在您真的不 想要自动移动到 bar
和一个空参数到 baz
。
当前要求您指定是否以及何时移动或转发参数的规则并非偶然。