【问题标题】:what is going on in std::function operator() and std::forward?std::function operator() 和 std::forward 发生了什么?
【发布时间】:2017-07-25 09:34:56
【问题描述】:

我正在查看 std::function 实现及其调用 operator()

template<typename Ret, typename... ArgTypes>
Ret function< Ret (ArgTypes...)>::operator()(ArgTypes...args) const
{
  // some stuff
  return invoker(functor, std::forward<ArgTypes>(args)...);
}

我特别想知道,为什么这里使用std::forward?这与完美转发有什么关系吗? 因为只有当operator() 是一个带有可变参数模板声明template&lt;typename... Args&gt; 的模板(它不是,声明是 std::function 的部分特化)时,才能完成完美转发。 在这里使用 std::forward 的目的是什么?我很困惑:-)?

【问题讨论】:

  • 它不是’ 不是吗?对我来说当然是这样。
  • 声明是 std::function 和 templ.参数Ret, ArgTypes 已经修复
  • 对我来说看起来像是类模板的部分特化的成员函数。我不确定你在哪里看到完整的专业化(完整的专业化将有空的模板参数列表)。
  • @Dmitry Kuznetsov 你是对的!

标签: c++ c++11 c++14 std-function perfect-forwarding


【解决方案1】:

你说得对,这不是典型的“完美转发”场景。一个简短的例子可以帮助说明动机。假设类型 A 带有检测的构造函数和析构函数:

#include "A.h"
#include <functional>
#include <iostream>

int
main()
{
    A a1{1};
    A a2{2};
    std::function<void(A, A&)> f{[](A x, A& y){}};
    f(a1, a2);
}

这将输出:

A(int state): 1
A(int state): 2
A(A const& a): 1
A(A&& a): 1
~A(1)
~A(-1)
~A(2)
~A(1)

解释:

a1a2 在堆栈上构造。然后,当传递到function 调用程序时,首先复制a1 以绑定到第一个按值参数,然后在a1 上调用std::forward&lt;A&gt;,将其移动 -value 参数到 lambda 中。

相比之下,a2 不需要复制到绑定到functionA&amp; 参数,然后调用std::forward&lt;A&amp;&gt;(a2),它将a2 作为左值而不是右值转发,这将绑定到A&amp; lambda 的参数。

然后事情就被破坏了。 ~A(-1) 表示使用此检测的 A 销毁处于 move-constructed-from 状态的 A

总而言之,即使ArgTypes 没有像通常的完美转发习语那样推导出来,我们仍然希望将ArgTypes 作为右值转发,并将ArgTypes 作为左值转发。所以std::forward 恰好在这里做了我们想要的。

【讨论】:

  • @Gabriel:对不起,我不明白后续问题。你能说得更具体点吗?
  • 也可以这样表述(对我来说更清楚一点):...然后调用std::forward&lt;A&gt;(a1),返回A&amp;&amp;a1(这是一个右值引用->将其移入拉姆达)。 ...然后调用std::forward&lt;A&amp;&gt;(a2),它返回A&amp; &amp;&amp; a2,它折叠成A&amp; a2(这是一个左值引用,-> 作为左值转发)。
  • @Gabriel: std::function 如上所述。如果这就是您问题中“那个”的意思,那么标准中已经是这样了。对不起,如果我很密集。我现在已经醒了太多小时了。
【解决方案2】:

我想你对这里的很多事情感到困惑。

首先,完美转发与可变参数模板无关。您可以创建一个包装类,该类具有一个接受一个参数并将其转发给包装对象的函数:

template<typename T>
struct Wrapper {
    template<typename Arg>
    decltype(auto) test(Arg&& arg) {
        return t.test(std::forward<Arg>(arg));
    }

    T t;
};

注意这里使用了完美转发,没有任何可变参数模板。如果t.test 需要一个仅移动类型作为参数,那么没有forward&lt;Arg&gt;(arg) 就无法调用它。


这里发生的第二件事是&amp;&amp; 后面没有跟随参数。将&amp;&amp; 添加到ArgTypes 将是一个错误,并且会使某些情况无法编译。考虑这个简单的案例:

std::function<void(int)> f;
int i = 0;
f(i);

那将无法编译。如果将&amp;&amp; 添加到ArgTypes,则每个未引用的参数(例如int)都将成为调用运算符(在我们的例子中为int&amp;&amp;)的右值引用。由于std::function 参数列表中的所有参数类型都已正确限定,因此您希望在调用运算符中接收的正是这些类型,而不是转换。

如果不使用&amp;&amp;,为什么需要std::forward?因为即使您不需要推断值类别,您仍然不需要将每个参数都复制到包含的函数中。如果std::function 的参数之一是int&amp;,您不想移动它。但是如果其中一个参数是std::unique_ptr&lt;int&gt;,则必须移动它!这正是std::forward 的用途。只移动应该移动的东西。

【讨论】:

    【解决方案3】:

    std::forward 只是将右值引用附加到类型,因此考虑到引用折叠规则,它有效地按原样传递引用参数并移动对象参数。

    【讨论】:

      猜你喜欢
      • 2016-01-09
      • 1970-01-01
      • 2014-06-12
      • 2016-09-04
      • 1970-01-01
      • 1970-01-01
      • 2022-01-05
      • 2015-07-18
      相关资源
      最近更新 更多