【问题标题】:How to determine the type of a lambda which captures a noncopyable parameter?如何确定捕获不可复制参数的 lambda 类型?
【发布时间】:2020-09-24 23:54:31
【问题描述】:

鉴于以下不可复制的任务类和示例代码

#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 声明 test 变量,程序编译并运行完美,否则它将拒绝编译并出现此错误:

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 应该以某种方式声明为可变的?

【问题讨论】:

  • 出于兴趣,如果您将其弹出到调试器或智能 IDE 中,auto 将类型解析为什么?可能是一些您可以使用的好信息。
  • 我很笨。这有点问题,不是吗?
  • 打开的标签太多 :(
  • @NathanOliver 啊,完美。将其添加到重复列表中。
  • @dgmz 为了更直接地解决您的问题,每个 lambda 都有自己的匿名类型。但是,您可以使用decltype(lambda) test = std::move(lambda); 引用类型。

标签: c++ lambda move noncopyable


【解决方案1】:

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

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

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

如果你真的想用 lambdas 做到这一点并且你可以访问 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's answer)更有意义。

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

【讨论】:

  • 现在这是一个答案!
  • 感谢您的回答。即使在我的情况下,测试成员是一个类的一部分,task_runner_t 最终将成为构造函数的模板(而不是类)。但实际上您正在回答这个确切的问题,并且我找到了一种方法来使用指向封装函子的类的唯一指针将函子存储在测试成员中,这样我现在就没有问题来存储函子了!
【解决方案2】:

解决此问题的一种方法是放弃 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

【讨论】:

  • 感谢您的回答。我了解您的解决方案,这允许我们自己确定类型。就我而言,虽然为我拥有的每种类型的任务(不同的捕获参数等)创建一个 pseudo_lambda 类会非常昂贵
  • 我会说乏味,而不是昂贵。没有运行时开销。 lambda 只是我发布的代码的装饰。
  • 嗯,通常乏味意味着时间,不仅是我的时间,还有其他人的时间来维护代码库。既然时间就是金钱,至少对我的雇主来说,我坚持我的选择;)
猜你喜欢
  • 1970-01-01
  • 2020-04-17
  • 1970-01-01
  • 1970-01-01
  • 2019-10-12
  • 2011-12-18
相关资源
最近更新 更多