如何确定捕获不可复制参数的 lambda 的类型?

How to determine the type of a lambda which captures a noncopyable parameter?

本文关键字:lambda 类型 参数 可复制 何确定      更新时间:2023-10-16

给定不可复制的任务类和下面的示例代码

#include <functional>
#include <iostream>
#include <string>
class Task
{
public:
Task()
{
}
Task(const Task& other) = delete;
Task& operator=(const Task& other) = delete;
Task(Task&& other) = default;
Task& operator=(Task&& other) = default;
void operator()() const
{
std::cout << "Task !" << std::endl;
}
};

int main()
{  
auto task = Task();
auto lambda = [task = std::move(task)]
{
task();
};
std::function<void()> test = std::move(lambda);
test();
}

如果我用 auto 类型而不是std::function 声明测试变量,程序可以完美编译并运行,否则它将拒绝编译并出现此错误

functional:1878:34: error: use of deleted function 'main()::<lambda()>::<lambda>(const main()::<lambda()>&)'
__dest._M_access<_Functor*>() =
^
31:42: note: 'main()::<lambda()>::<lambda>(const main()::<lambda()>&)' is implicitly deleted because the default definition would be ill-formed:
31:42: error: use of deleted function 'Task::Task(const Task&)'
13:5: note: declared here

我真的需要声明测试的类型,因为它最终将是另一个类的成员。

我该怎么做?

我假设std::function应该以某种方式声明为可变的是对的吗?

当您想要引用foo的类型时,可以使用decltype(foo)作为类型。因此,您可以这样做:

decltype(lambda) test = std::move(lambda);

但是,您声明的目标是将其用作类成员。 在这种情况下,您需要一些东西来"窃取"类型。 请注意,编译器没有义务(据我所知(统一两个相同的 lambda 表达式的类型。 这意味着类型和 lambda 创建都必须取自同一个 lambda 表达式。

如果您真的想使用 lambda 执行此操作,并且可以访问 C++14(对于推导的返回类型(,那么您可以执行以下操作:

auto make_task_runner(Task task) {
return [task = std::move(task)]() { task(); };
}

这给了我们一个函数,我们既可以使用它来创建 lambda,也可以使用它来窃取类型(通过在函数的调用中使用decltype()(。

然后,在您的课堂上,您可以拥有:

class SomeClass {
// Type alias just to make things simpler.
using task_runner_t = decltype(make_task_runner(std::declval<Task>()));
task_runner_t task_runner;
}

然后,可以使用make_task_runner函数分配给此数据成员:

task_runner = make_task_runner(std::move(some_task));

但是,在这一点上,您已经失去了 lambda 的主要优势:能够即时创建新的短期未命名函数。 现在我们有一个命名函数来创建 lambda 对象,并且我们已经给 lambda 类型起了一个名字 (task_runner_t(,那么使用 lambda 来解决这个问题有什么意义呢?

在这种特殊情况下,自定义函子(如 Paul 的答案(更有意义。

。但是,Task已经是一个函子,因此您已经拥有了所需的类型:Task!只需使用它,而不是发明一个没有明显好处的包装器。

解决这个问题的一种方法是放弃 lambda 为您提供的语法糖,而是使用函子自己做,例如:

#include <functional>
#include <iostream>
#include <string>
class Task
{
public:
Task()
{
}
Task(const Task& other) = delete;
Task& operator=(const Task& other) = delete;
Task(Task&& other) = default;
Task& operator=(Task&& other) = default;
void operator()() const
{
std::cout << "Task !" << std::endl;
}
};
class pseudo_lambda
{
public:
pseudo_lambda (Task &&task) { m_task = std::move (task); }  // <- capture
void operator()() const { m_task (); }                      // <- invoke
private:
Task m_task;                                                // <- captured variable(s)
};
int main()
{  
auto task = Task();
pseudo_lambda pl { std::move (task) };
pl ();
}

现场演示