移动一个lambda:一旦你已经move-capture了一个只能移动的类型,lambda如何使用?

28

这个答案解释了如何在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++给出了类似的错误。

我尝试明确地移动lambda(尽管它是一个临时值),但没有帮助。

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

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


我认为setCallback应该通过值而不是rvalue引用来获取参数,我错了吗? - Slava
@Slava 这就是我最初的代码,但是它产生了同样的错误。我以为使用rvalue引用会允许(或强制)lambda函数被移动构造,但事实并非如此。 - Kyle Strand
2个回答

25

你可以移动lambda表达式,没问题。不过这并不是你的问题所在,你试图用一个不可复制的lambda实例化一个std::function对象。错误信息如下:

template< class F > 
function( F f );

function的构造函数:

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

这是因为std::function

满足可复制构造和可复制赋值的要求。

由于function必须可复制,因此放入其中的所有内容也必须可复制。而只能移动的lambda表达式不符合该要求。


哦,谢谢您——即使对于这个简单的情况,我也很难解析那些错误消息。除了使用通用引用限定的模板参数而不是std::function之外,有没有改变签名以使其工作的方法? - Kyle Strand
@KyleStrand,无论参数是什么,你都不能构造function。如果你需要某种类型抹除,你必须编写可移动的function等效物。 - Barry
@Barry,已经有更多只能移动的std :: functions替代品了,只是提到两个:https://github.com/Naios/Function2和https://github.com/potswa/cxx_function - Naios
@Barry 我的意思是直接使用 lambda(而不是将其转换为 function 或任何其他类似类型),通过编写一个模板函数,该函数将使用 lambda 本身作为模板类型。 - Kyle Strand
1
@KyleStrand 当然没问题,将 lambda 表达式移动到其他地方也完全没有问题。 - Barry

18

std::function 不是 lambda!它是一个包装器,可以从任何类型的可调用对象构造,包括 lambda。 std::function 要求 可调用对象可进行复制构造,这就是为什么你的示例失败的原因。

可以像下面展示的那样再次移动只移动 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));
}

现场演示


我知道std::function不是lambda,但由于lambda可以转换为std::function,所以我没有在脑海中清晰地区分它们。 - Kyle Strand
那么如何在原始代码中实现HasCallback呢?我的意思是,如何将只移动的lambda保存到只移动的容器或只移动的数据结构中? - alpha
@alpha 你需要直接使用lambda而不将其转换为另一种类型(即调用lambda的函数需要将lambda作为模板类型参数),或者使用不同的函数类(野外有各种std :: function的替代品,或者可以自己设计)。 - Kyle Strand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接