3

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 ?

dgmz
  • 394
  • 3
  • 16
  • Out of interest, if you pop that into a debugger or smart IDE, what does it say `auto` resolved the type to? Could be some good information you can use. – user4581301 Jun 05 '20 at 17:32
  • Me am dumb. That's kinda the question, isn't it? – user4581301 Jun 05 '20 at 17:33
  • too many dang tabs open :( – NathanOliver Jun 05 '20 at 17:41
  • @NathanOliver Ah, perfect. Added that to the duplicate list. – cdhowie Jun 05 '20 at 17:44
  • 1
    @dgmz To address your question more directly, each lambda has its own anonymous type. However, you can refer to the type with `decltype(lambda) test = std::move(lambda);`. – jtbandes Jun 05 '20 at 17:45
  • @NathanOliver the duplicate doesn't seem suitable to me. I do not see it answering the question of `test`s type. – SergeyA Jun 05 '20 at 17:45
  • @SergeyA I didn't close it as a dupe, just posted a link to the work around and why it doesn't work vwhen using a `std::function`. – NathanOliver Jun 05 '20 at 17:46
  • @NathanOliver, yes, it wasn't you, sorry. Reopening. – SergeyA Jun 05 '20 at 17:47
  • I have reopened the question, because the duplicate suggested fails to answer the actual question. – SergeyA Jun 05 '20 at 17:48
  • For why `std::function` doesn't work, and how you could work around it, see: https://stackoverflow.com/questions/25421346/how-to-create-an-stdfunction-from-a-move-capturing-lambda-expression – NathanOliver Jun 05 '20 at 17:49
  • @SergeyA The question is about storing a lambda in a data member. I see this going down the rabbit hole of "you have to use `decltype()` there" which is going to be an ABI nightmare if this thing is part of a larger project that will become a library. At which point `std::function` is going to be the route one needs to take, and then we're right back here. – cdhowie Jun 05 '20 at 17:49
  • @cdhowie still, there is long road from the question to duplicate, and the link is not immediately obvious to me. I think, a proper answer might be in order. – SergeyA Jun 05 '20 at 17:53

2 Answers2

4

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.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • Now this is an answer! – SergeyA Jun 05 '20 at 18:07
  • 1
    Thank you for this answer. Even though in my case the test member is part of a class for which task_runner_t would end up being a template of the constructor (not the class). But indeed you are answering this exact question, and I have found a way to store the functor in the test member using a unique pointer to a class encapsulating the functor, so that I have no problems anymore to store the functor now ! – dgmz Jun 05 '20 at 22:00
2

One way to approach this is to forego the syntactic sugar that a lambda offers you and do it yourself with a functor instead, e.g.:

#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

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • Thank you for your answer. I understand your solution and this allows to determine the type by ourselves. In my case though it would be pretty costly to create a pseudo_lambda class for each type of task that I have (different capture parameters, etc.) – dgmz Jun 05 '20 at 22:11
  • I would say tedious, rather than costly. There's no runtime overhead. A lambda is just window dressing for the code I posted. – Paul Sanders Jun 05 '20 at 23:03
  • Well, usually tediousness would imply time and not only my time but the other people’s time who come after me to maintain the code base. And since time is money, at least for my employer, I stand by my choice of word ;) – dgmz Jun 07 '20 at 00:28