【问题标题】:std::invoke - perfect forwarding functorstd::invoke - 完美的转发函子
【发布时间】:2021-09-02 04:32:32
【问题描述】:

试图理解为什么下面的例子编译失败:

#include <functional>

template <typename F>
void f1(F&& f)
{
  std::forward<F>(f)("hi");
}

template <typename F>
void f2(F&& f)
{
  std::invoke(f, "hi");  // works but can't perfect forward functor
}

template <typename F>
void f3(F&& f)
{
  std::invoke<F>(f, "hi");
}

int main()
{
  f1([](const char*) {});  // ok
  f2([](const char*) {});  // ok
  f3([](const char*) {});  // error
}

cppreference 说以下关于std::invoke

  1. 使用参数 args 调用 Callable 对象 f。正如INVOKE(std::forward&lt;F&gt;(f), std::forward&lt;Args&gt;(args)...)。仅当 std::is_invocable_v&lt;F, Args...&gt; 为 true 时,此重载才参与重载解析。

那么为什么f3 不等于f1

【问题讨论】:

  • @JeJo 我不认为f1 中的std::forward 是多余的。 F 可以是一种行为不同的类型,具体取决于它的 operator() 是在 F 的左值还是右值上调用:godbolt.org/z/f3sjcPK14

标签: c++ c++17


【解决方案1】:

std::invoke 本身就是一个函数。在您的情况下,它的第一个参数是右值引用,而 f 是左值,因此会发生错误。

INVOKE(std::forward&lt;F&gt;(f), std::forward&lt;Args&gt;(args)...) 在函数std::invoke 被正确选择和调用后执行。基本上,您的 lambda 函数按如下方式传递:

original lambda in main -> the parameter of f3 -> the parameter of std::invoke -> the parameter of INVOKE

所以最后一步使用INVOKE(std::forward&lt;F&gt;(f), std::forward&lt;Args&gt;(args)...)中的std::forward,而中间需要转发lambda(f3的参数->std::invoke的参数)。我想这就是你的困惑所在。

【讨论】:

    【解决方案2】:

    因为你需要std::forward&lt;F&gt;(f)std::invoke()

    template <typename F>
    void f3(F&& f)
    {
      std::invoke<F>(std::forward<F>(f), "hi"); // works
    }
    

    考虑这两个调用之间的区别:

    void f(const int&) { std::cout << "const int&" << std::endl; }
    void f(int&&) { std::cout << "int&&" << std::endl; }
    
    int main()
    {
      std::cout << "first" << std::endl;
      int&& a = 3;
      f(a);
      std::cout << "second" << std::endl;
      int&& b = 4;
      f(std::forward<int>(b));
    }
    

    输出是

    first
    const int&
    second
    int&&
    

    如果您删除 const int&amp; 重载,您甚至会在第一次调用时遇到编译器错误:

    error: cannot bind rvalue reference of type 'int&&' to lvalue of type 'int'
    

    std::forward() 是向std::invoke() 传递正确类型所必需的。

    【讨论】:

    • 我明白了。而且我认为std::invoke&lt;F&gt;(std::forward&lt;F&gt;(f), "hi");std::invoke(std::forward&lt;F&gt;(f), "hi"); 是等价的?
    【解决方案3】:

    我猜你遇到了这个错误:

    注意:模板参数扣除/替换失败:
    注意:不能将'f'(类型'main()::')转换为类型'main()::&&'

    这是因为在函数 f3 内部,f 是一个 L 值,但 invoke 需要一个 R 值。为了使模板参数推导/替换起作用,类型必须完全匹配。

    当您将 f 完美转发到 invoke 时,此问题已解决,因为您最初从外部 f3 传递了 R 值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-09-07
      • 2015-04-11
      • 2014-01-04
      • 1970-01-01
      • 1970-01-01
      • 2012-06-23
      • 2021-12-17
      相关资源
      最近更新 更多