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

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

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

#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();
}

如果我声明 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

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

我该怎么做?

我认为 std::function 应该以某种方式声明为可变的吗?

解决这个问题的一种方法是放弃 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 ();
}

Live demo

当你想引用foo的类型时,你可以使用decltype(foo)作为类型。所以,你可以这样做:

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

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

如果你真的想用 lambdas and 来做到这一点,你可以访问 C++14(对于推导的 return 类型),那么你可以这样做:

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

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

然后,在您的 class 中,您可以:

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 来解决这个问题还有什么意义呢?

在这种特殊情况下,自定义仿函数(如 )更有意义。

... 然而,Task 已经是一个仿函数 所以你已经有了你需要的类型:Task!只需使用它而不是发明一个没有明显好处的包装器。