c++lambdamovenoncopyable

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


Given the non copyable Task class and sample code below

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

If I declare the test variable with type auto instead of std::function, the program compiles and runs perfectly, otherwise it will refuse to compile with this error:

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

I really need to declare the type of test since it will be in the end a member of another class.

How do I do that ?

Am I right to suppose that std::function should be in some way declared mutable ?


Solution

  • You can use decltype(foo) as a type when you want to refer to the type of foo. So, you could do this:

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

    However, your stated goal is to use this as a class member. In that case you need something to "steal" the type from. Note that the compiler is under no obligation (as far as I know) to unify the types of two identical lambda expressions. This means that both the type and the lambda creation must be taken from the same lambda expression.

    If you really want to do this with lambdas and you have access to C++14 (for deduced return types) then you could do something like:

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

    This gives us a function that we can use both to create the lambdas, and to steal the type (by using decltype() over an invocation of the function).

    Then, in your class you could have:

    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;
    }
    

    You can then assign to this data member by using the make_task_runner function:

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

    However, at this point you've already lost the primary benefit of lambdas: the ability to create a new short-lived, unnamed function on-the-fly. Now we have a named function to create the lambda object and we've given the lambda type a name (task_runner_t), so what is even the point of using lambda to solve this problem anymore?

    In this particular case, a custom functor (as in Paul's answer) makes a lot more sense.

    ... However, Task is already a functor so you already have exactly the type you need: Task! Just use that instead of inventing a wrapper for no apparent benefit.