在像这样的功能模板中
template <typename T>
void foo(T&& x) {
bar(std::forward<T>(x));
}
如果使用右值引用调用x
,foo
内的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 << "\033[1;35m" << boost::typeindex::type_id<T>().pretty_name()
<< "\033[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&&>
修改:我找到了这个答案:https://stackoverflow.com/a/27409428/2640636显然,
一旦给参数命名,它就是左值。
我的问题是,为什么选择这种行为而不是将rvalue引用保持为rvalues,即使它们被赋予了名称?在我看来,整个转发的考验都可以这样规避。
Edit2 :我不是在询问 std::forward
的内容。我问的是为什么需要它。
答案 0 :(得分:4)
不是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
将始终是左值,因此您将失去行动需要时的语义,最终可能会有不必要的副本等。
您可以看到差异的简单示例:
#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
}
答案 1 :(得分:2)
您确实不希望您的参数自动移动到所调用的函数。考虑这个功能:
template <typename T>
void foo(T&& x) {
bar(x);
baz(x);
global::y = std::forward<T>(x);
}
现在你真的没有希望自动转移到bar
,将空参数转移到baz
。
要求您指定是否以及何时移动或转发参数的当前规则不是偶然的。
答案 2 :(得分:1)
无论有没有
,我都会得到完全相同的类型std::forward<T>
...没有?你自己的输出证明你错了:
__<int> // T
__<int&&> // decltype(x)
__<int&&> // std::forward<T>(x)
不使用std::forward<T>
或decltype(x)
,您将获得int
而不是int&&
。这可能无意中无法&#34;传播x
的右值&#34; - 请考虑以下示例:
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&&"
}
答案 3 :(得分:0)
x
与具有r值引用类型的x
不同。R值是表达式的属性,而r值引用是其类型的属性。
如果您实际尝试将作为r值引用的变量传递给函数,则将其视为l值。 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
与4wrapIRiE
对4wrapIRKiE
与4wrapIRKiE
的对比4wrapIiE
与4wrapIRiE
相对应(这些应该匹配!)
4wrapIKiE
与4wrapIRKiE
相对应(这些应匹配!)