2016-01-15 78 views
7

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

的示例代码是仙女简单:

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 ::函数对象,封装所述功能参数:

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功能]),那将非常酷...

+0

如果传递'自动p_fn会发生什么= &fn'到lambda? –

+0

@JoelCornett sigsegv或类似的东西。同样的事情发生在传递'fn'作为参考。虽然它编译,在这两种情况下。 – Marandil

回答

5

变量fn具有函数类型。特别是,它有Fn = int (&)(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不会做太大。 (移动一个常量值没有太大的作用)。

第三,你可以移动的承诺中,而不是创建一个不必要的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不了)。

+0

哇,非常感谢您的建议,但不幸的是,其中大多数需要C++ 14,我决定坚持C++ 11的项目(两个编译器抱怨使用C++ 14扩展时使用初始化lambda捕捉,而我试图坚持纯粹的C + + 11),所以承诺必须留一个指针(虽然我猜它可能只是正常的指针,但我不知道在这两种情况下会发生什么,当lambda永远不会运行...)。 一旦我准备用C++写一些东西的话,一定会使用你的建议;) – Marandil

+2

@Marandil移入lambda可以手动编写为帮助类型(这有点冗长,我承认)。你可以用'auto my_fn = std :: forward (fn);'在本地保存'fn'的副本来解决衰变问题,然后捕获'my_fn'。请注意,我上面的C++ 14版本(和你的)不必要地将'args ...'复制到lambda中:这是低效的。 – Yakk

0

你的第一个代码编译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(); 
};