移动 lambda:一旦你移动捕获了只移动类型,如何使用 lambda? [复制]

Posted

技术标签:

【中文标题】移动 lambda:一旦你移动捕获了只移动类型,如何使用 lambda? [复制]【英文标题】:Moving a lambda: once you've move-captured a move-only type, how can the lambda be used? [duplicate] 【发布时间】:2015-12-05 20:09:45 【问题描述】:

This answer 解释了如何在 C++14 中移动捕获 lambda 中的变量。

但是,一旦您在 lambda 中移动捕获了一个不可复制的对象(例如 std::unique_ptr),您就无法复制 lambda 本身。

如果您可以移动 lambda,这会很好,但尝试这样做时会出现编译错误:

using namespace std;

class HasCallback

  public:
    void setCallback(std::function<void(void)>&& f)
    
      callback = move(f);
    

    std::function<void(void)> callback;
;

int main()

  auto uniq = make_unique<std::string>("Blah blah blah");
  HasCallback hc;
  hc.setCallback(
      [uniq = move(uniq)](void)
      
        std::cout << *uniq << std::endl;
      );

  hc.callback();

这会产生以下g++ 错误(我试图仅复制相关行):

error: use of deleted function ‘main()::<lambda()>::<lambda>(const main()::<lambda()>&’

...我认为,这意味着我移动 lambda 的尝试失败了。

clang++ 给出了类似的错误。

我尝试明确地moveing lambda(即使它是一个临时值),但这并没有帮助。

编辑: 下面的答案充分解决了上述代码产生的编译错误。对于另一种方法,只需将唯一指针的目标值release 转换为std::shared_ptr可以复制。 (我不是在写这个作为答案,因为那会假设这是一个 XY 问题,但是 unique_ptr 不能在转换为 std::function 的 lambda 中使用的根本原因很重要理解.)

编辑 2: 有趣的是,据我所知,我刚刚意识到 auto_ptr 实际上会在这里做正确的事情(!)。它的行为本质上类似于unique_ptr,但允许复制构造代替移动构造。

【问题讨论】:

我认为 setCallback 应该通过值而不是右值引用来获取参数,我错了吗? @Slava 这就是我最初拥有的,但它给出了同样的错误。我认为采用右值引用将允许(/强制)移动构造 lambda,但情况似乎并非如此。 【参考方案1】:

你可以移动lambda,没关系。不过,这不是您的问题,您正在尝试使用不可复制的 lambda 实例化 std::function。还有:

template< class F > 
function( F f );

function 的构造函数:

5) 使用f副本 初始化目标。

这是因为std::function:

满足 CopyConstructible 和 CopyAssignable 的要求。

由于function 必须是可复制的,因此您放入其中的所有内容也必须是可复制的。而只能移动的 lambda 不满足该要求。

【讨论】:

....呵呵。谢谢 - 即使对于这个简单的案例,我也很难解析这些错误消息。除了使用通用引用限定模板参数而不是 std::function 之外,有什么方法可以更改签名以工作? @KyleStrand 参数是什么并不重要,你只是不能构造function。如果您需要类型擦除的内容,则必须编写可移动的 function 等效项。 @Barry 已经有了 move only std::functions 替代品,只提两个:github.com/Naios/Function2 和 github.com/potswa/cxx_function @Barry 我的意思是直接使用 lambda(不将其转换为 function 或任何其他类似类型),方法是编写一个将 lambda 本身用作模板类型的模板函数。 @KyleStrand 哦,当然,没关系。移动 lambda 是非常好的。【参考方案2】:

std::function 不是 lambda!它是一个可以从任何类型的可调用对象(包括 lambda)构造的包装器。 std::function 需要 callable be copy-constructible,这就是您的示例失败的原因。

一个只能移动的 lambda 可以再次移动,如下所示。

template<typename F>
void call(F&& f)

    auto f1 = std::forward<F>(f);  // construct a local copy
    f1();


int main()

  auto uniq = make_unique<std::string>("Blah blah blah");
  auto lambda = [uniq = move(uniq)]() 
        std::cout << *uniq << std::endl;
      ;
//  call(lambda);   // doesn't compile because the lambda cannot be copied
  call(std::move(lambda));

Live demo

【讨论】:

确认。我知道 std::function 不是 lambda,但由于 lambda 可以转换为 std::function,所以我并没有清楚地记住这个区别。 那么如何在原代码中实现HasCallback呢?我的意思是,如何将仅移动 lambda 保存到仅移动容器或仅移动数据结构中? @alpha 您要么需要直接使用 lambda 而不将其转换为另一种类型(即调用 lambda 的函数需要将 lambda 作为模板类型参数),要么使用不同的函数类(std::function 在野外有多种替代方案,或者您可以自己设计)。

以上是关于移动 lambda:一旦你移动捕获了只移动类型,如何使用 lambda? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

将 packaged_task 对象移动到 lambda 捕获时出错

如果 lambda 使用 std::move() 捕获不可复制的对象,为啥它是不可移动的?

在 c++14 lambda 表达式中捕获和移动 unique_ptr

剑道移动应用程序中的剑道调度程序

事件过滤器未捕获 Qt 鼠标移动事件

Telerik Kendo MVC 调度程序 - 无法移动事件