【问题标题】:C++11 Passing function as lambda parameterC ++ 11将函数作为lambda参数传递
【发布时间】:2016-04-21 07:41:08
【问题描述】:

最近,我在将作为参数传递的函数传递给 lambda 表达式时遇到了一个奇怪的问题。代码在 clang 3.5+ 中编译得很好,但在 g++ 5.3 中编译失败,我想知道问题出在 c++ 标准、clang 中的非标准扩展还是 GCC 中的无效语法解释。

示例代码非常简单:

    template<typename Fn, typename... Args, typename T = typename std::result_of<Fn(Args...)>::type>
    std::future<T>
    async(Fn &&fn, Args &&... args)
    {
        std::shared_ptr<std::promise<T>> promise = std::make_shared<std::promise<T>>();
        auto lambda = [promise, fn, args...](void)
            { promise->set_value(fn(std::move(args)...)); };
        send_message(std::make_shared<post_call>(lambda));
        return promise->get_future();
    };

GCC 报告了以下内容:

error: variable ‘fn’ has function type
             { promise->set_value(fn(std::move(args)...)); };
(...)
error: field ‘async(Fn&&, Args&& ...) [with Fn = int (&)(int, int); Args = {int&, int&}; T = int]::<lambda()>::<fn capture>’ invalidly declared function type
         auto lambda = [promise, fn, args...](void)

还好我找到了一个简单的变通方法,添加一个std::function对象,封装函数参数:

    template<typename Fn, typename... Args, typename T = typename std::result_of<Fn(Args...)>::type>
    std::future<T>
    async(Fn &&fn, Args &&... args)
    {
        std::shared_ptr<std::promise<T>> promise = std::make_shared<std::promise<T>>();
        std::function<T(typename std::remove_reference<Args>::type...)> floc = fn;
        auto lambda = [promise, floc, args...](void)
            { promise->set_value(floc(std::move(args)...)); };
        send_message(std::make_shared<post_call>(lambda));
        return promise->get_future();
    };

虽然我不完全理解第一段代码有什么问题,但它成功地用 clang 编译并运行没有错误。


编辑

我刚刚注意到,如果其中一个参数应该是参考,我的解决方案将灾难性地失败。因此,如果您有任何其他可能适用于 C++11 的建议(即没有专门的 lambda 捕获 [c++14 功能]),那将是非常酷的...

【问题讨论】:

  • 如果将auto p_fn = &amp;fn 传递给 lambda 会发生什么?
  • @JoelCornett sigsegv 或类似的东西。传递fn 作为参考也发生了同样的事情。虽然它在这两种情况下都能编译。
  • @JoelCornett 在并发/异步环境中,将指针传递给作用域变量绝不是一个好主意。

标签: c++ c++11 gcc clang clang++


【解决方案1】:

变量fn 具有函数类型。特别是,它的类型为Fn = int (&amp;)(int, int)

Fn 类型的值(不是引用)的类型为 int(int,int)

无法存储此值。

我有点惊讶它不会为你自动衰减。在 C++14 中,你可以这样做:

auto lambda = [promise, fn=fn, args...](void)
        { promise->set_value(fn(std::move(args)...)); };

应该将fn(外部)的类型在内部衰减为fn。如果这不起作用:

auto lambda = [promise, fn=std::decay_t<Fn>(fn), args...](void)
        { promise->set_value(fn(std::move(args)...)); };

显式衰减它。 (衰减是一种使类型适合存储的操作)。

其次,你应该添加mutable

auto lambda = [promise, fn=std::decay_t<Fn>(fn), args...](void)mutable
        { promise->set_value(fn(std::move(args)...)); };

或者std::move 不会做太多事情。 (移动一个 const 值并没有多大作用)。

第三,您可以将 promise 移入,而不是创建不必要的共享 ptr:

template<typename Fn, typename... Args, typename T = typename std::result_of<Fn(Args...)>::type>
std::future<T>
async(Fn &&fn, Args &&... args)
{
    std::promise<T> promise;
    std::future<T> ret = promise.get_future();
    auto lambda =
      [promise=std::move(promise), fn=std::decay_t<Fn>(fn), args...]
      () mutable {
        promise.set_value(fn(std::move(args)...));
      };
    send_message(std::make_shared<post_call>(lambda));
    return ret;
};

这假定您的 post_call 类可以处理仅移动 lambda(如果它是 std::function 则不能)。

【讨论】:

  • 哇,非常感谢您的建议,但不幸的是,其中大多数都需要 C++14,我决定在项目中坚持使用 C++11(两个编译器都抱怨使用 c+使用初始化的 lambda 捕获时 +14 扩展,而我试图坚持纯 c++11),所以承诺必须保持一个指针(虽然我猜它可能只是普通指针,但我不太确定什么是在这两种情况下都会发生,当 lambda 永远不会运行时......)。一旦我要用 c++1y 写东西,肯定会使用你的建议;)
  • @Marandil Move-into lambda 可以手动编写为辅助类型(我承认这有点冗长)。您可以通过在本地使用auto my_fn = std::forward&lt;Fn&gt;(fn); 存储fn 的副本,然后捕获my_fn 来解决衰减问题。请注意,我上面的 C++14 版本(和你的)不必要地将 args... 复制到 lambda 中:这是低效的。
【解决方案2】:

您的第一个代码在 VS2015 下编译得很好,所以我无法重现您的问题,但由于您使用的是通用引用,我会尝试类似:

template<typename Fn, typename... Args, typename T = typename std::result_of<Fn(Args...)>::type>
std::future<T> async2(Fn &&fn, Args &&... args)
{
    std::promise<T> prom;
    auto floc = std::bind(std::forward<Fn>(fn), std::forward<Args>(args)...);
    auto lambda = [&prom, floc](void)
    { prom.set_value(floc()); };
    send_message(std::make_shared<post_call>(lambda));
    return prom.get_future();
};

【讨论】: