【问题标题】:Why doesn't my forward_ function work for rvalues?为什么我的 forward_ 函数不适用于右值?
【发布时间】:2020-12-01 20:43:46
【问题描述】:

我已经了解std::move 的工作原理,并实现了我自己的版本,仅供练习。现在我想了解std::forward 的工作原理:

到目前为止我已经实现了这个:

#include <iostream>


template <typename T>
T&& forward_(T&& x)
{
    return static_cast<T&&>(x);
}


/*template <typename T>
T&& forward_(T& x)
{
    return static_cast<T&&>(x);
}*/

void incr(int& i)
{
    ++i;
}

void incr2(int x)
{
    ++x;
}

void incr3(int&& x)
{
    ++x;
}

template <typename T, typename F>
void call(T&& a, F func)
{
    func(forward_<T>(a));
}


int main()
{

    int i = 10;
    std::cout << i << '\n';
    call(i, incr);
    std::cout << i << '\n';

    call(i, incr2);
    std::cout << i << '\n';

    call(0, incr3); // Error: cannot bind rvalue reference of type int&& to lvalue of type int.

    std::cout << "\ndone!\n";
}

为什么我必须提供采用左值引用的重载forward(T&amp;) 版本?据我了解,转发引用可以根据其参数的类型产生左值或右值。因此,将右值文字0 传递给call 以及采用int&amp;&amp; 类型的右值引用的incr3 函数通常不需要forward&lt;T&gt;(T&amp;)?!

  • 如果我取消注释 forward_(T&amp;) 版本,它可以正常工作!?

  • 我仍然很困惑:为什么如果我只使用forward_(T&amp;) 版本,它是否适用于任何价值类别?那么让那个转发引用forward_(T&amp;&amp;)有什么意义呢?

  • 如果我取消注释将左值引用引用到T&amp; 的版本和转发引用T&amp;&amp; 的版本,那么代码工作正常,我在两者中添加了一些消息以检查调用了哪个。结果是 T&amp;&amp; 从未调用过的那个!

      template <typename T>
      T&& forward_(T& x)
      {
          std::cout << "forward_(T&)\n";
          return static_cast<T&&>(x);
      }
    
      template <typename T>
      T&& forward_(T&& x)
      {
          std::cout << "forward_(T&&)\n";
          return static_cast<T&&>(x);
      }
    
  • 我的意思是在上面显示的驱动程序中运行相同的代码。

【问题讨论】:

  • 您不需要重载版本...forward(T&amp;) 是您应该拥有的唯一版本。 “我猜转发引用可以根据其参数的类型产生左值或右值。”不,引用的类型取决于它的模板参数而不是它的参数。
  • @BenVoigt:是的,如果我使用仅对T 进行左值引用的版本,它就可以工作,但是为什么标准添加了带有转发引用的版本? T&amp;&amp;?
  • @Maestro 见stackoverflow.com/q/27501400/580083。 TL;DR:它强制您为std::forward 提供显式模板参数。现场演示:godbolt.org/z/ed8GKb

标签: c++ perfect-forwarding


【解决方案1】:

如果您手动指定T(而不是让编译器推断它),T&amp;&amp; 引用将不再是转发引用。如果T 不是左值引用,那么T&amp;&amp; 是右值引用并且不会接受左值。

比如你做forward_&lt;int&gt;(...),那么参数就是一个右值引用,...只能是一个右值。

但是如果你做forward_(...),那么参数是一个转发引用,...可以有任何值类别。 (不过,这样称呼它是没有意义的,因为forward_(x) 将与x 本身具有相同的值类别。)

【讨论】:

  • 请多解释一下,我很困惑。
  • 我已经编辑了我的 call 函数来调用 func(forward_(a));, then func&lt;T&amp;&amp;&gt;(forward_(a)); 但仍然是同样的错误?!
  • @Maestro 是的,因为在不指定模板参数的情况下执行forward_(...) 是完全没用的(结果与参数具有相同的值类别)。
  • @Maestro:基本问题是call(T&amp;&amp; a, F func),ainside总是一个lvalue .即使call() 的参数作为右值到达,一旦它进入并被命名为a,它也是一个左值。 forward&lt;T&amp;&amp;&gt;(a) 将类别从左值放回它到达的类别。 a 在到达 forward() 时始终是左值。
  • @Maestro:Daniel already answered that one 当您在对该问题的评论中提问时。
【解决方案2】:

你很清楚为什么有两个版本的std::forward;一个采用对类型参数T&amp; 的左值引用,另一个采用对类型参数的通用引用(转发)。 T&amp;&amp;.

在您的情况下,您在函数模板call 内部使用forward_,该模板也具有转发引用。问题是即使使用右值调用的函数call 也总是使用forward_ 作为左值,因为call 无法在没有对象(参数)的情况下传递其参数。请记住,即使它是从 r 值初始化的,对象的名称也是 lvlaue。这就是为什么在您的示例中总是调用forward_(T&amp;)

  • 现在你问为什么有第二个版本的转发引用? 它非常简单,您可能已经猜到了:它用于 r 值(这些值不是这些对象的名称)。

这是一个例子:

template <typename T>
T&& forward_(T& x)
{
    std::cout << "forward_(T&)\n";
    return static_cast<T&&>(x);
}

template <typename T>
T&& forward_(T&& x)
{
    std::cout << "forward_(T&&)\n";
    return static_cast<T&&>(x);
}

int main()
{

    int i = 10;
    forward_(i); // forward(T&)  (1)
    forward_(5); // forward(T&&) (2)
    forward_("Hi"); // forward(T&) (3)
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-03
    • 1970-01-01
    • 1970-01-01
    • 2021-02-28
    • 1970-01-01
    • 1970-01-01
    • 2012-10-29
    • 2020-08-31
    相关资源
    最近更新 更多