为什么转发引用需要 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.outgcc 6.2.0boost 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 中的右值引用吗?

不,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 始终是左值 ,因此你将失去移动语义需要时,最终可能会得到 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
}

Live on Coliru

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

wandbox example

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

当前要求您指定是否以及何时移动或转发参数的规则并非偶然。