为什么std :: forward需要转发引用

时间:2017-02-18 21:43:15

标签: c++ c++11 move-semantics perfect-forwarding forwarding-reference

在像这样的功能模板中

template <typename T>
void foo(T&& x) {
  bar(std::forward<T>(x));
}

如果使用右值引用调用xfoo内的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.0boost 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的内容。我问的是为什么需要它。

4 个答案:

答案 0 :(得分:4)

不是foo里面的左右参考吗?

不,xfoo里面的左值(它有一个名称和地址),类型为右值参考。将其与参考折叠规则和模板类型扣除规则相结合,您将看到需要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
}

Live on Coliru

答案 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&&"
}

wandbox example

答案 3 :(得分:0)

作为r值的

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));
}

输出:

  

4wrapIRiE4wrapIRiE对   4wrapIRKiE4wrapIRKiE的对比   4wrapIiE4wrapIRiE相对应(这些应该匹配!)
  4wrapIKiE4wrapIRKiE相对应(这些应匹配!)