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

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

<分区>

在这样的函数模板中

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.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显然,

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始终是左值,因此您将在需要时失去 move 语义,并可能最终得到不必要的拷贝等。

您可以看到其中差异的简单示例:

#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

关于c++ - 为什么转发引用需要 std::forward,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42321009/

相关文章:

c++ - 是否需要销毁移出的对象?

c++ - Automake AM_LDADD 解决方法

c++ - 从子类中隐藏变量

c++ - 关于增量和赋值的解释

python - 尝试将数据从 C++ 发送到 Python 并使用套接字 C++、C++ sendto() 和 python recvfrom() 工作但不是反向

c++ - 为什么 CMake 似乎不使用 add_compile_options 命令应用 -pthread ?

C++11:未触发 move 构造函数

C++宏解释

c++ - 通过引用传递的函数接受对带有operator()的类的引用

c++ - 传递左值时使用 std::move