1

The Problem When creating schedulers the last copy or move of a function object is the last place that the function object is ever referenced (by a worker thread). If you were to use a std::function to store functions in the scheduler then any std::promises or std::packaged_task or other similarly move only types don't work as they cannot be copied by std::function.

Similarly, if you were to use std::packaged_task in the scheduler it imposes unnecessary overhead as many tasks do not require the std::future returned by packaged task at all.

The common and not great solution is to use a std::shared_ptr<std::promise> or a std::shared_ptr<std::packaged_task> which works but it imposes quite a lot of overhead.

The solution A make_owner, similar to make_unique with one key difference, a move OR copy simply transfers control of destruction of the object. It is basically identical to std::unique_ptr, except that it is copyable (it basically always moves, even on a copy). Grosss....

This means that moving of std::functions doesn't require copies of the std::shared_ptr which require reference counting and it also means there is significantly less overhead on the reference counting etc. A single atomic pointer to the object would be needed and a move OR copy would transfer control. The major difference being that copy also transfers control, this might be a bit of a no-no in terms of strict language rules but I don't see another way around it.

This solution is bad because:

  • It ignores copy symantics.
  • It casts away const (in copy constructor and operator =)

Grrr It isn't as nice of a solution as I'd like so if anybody knows another way to avoid using a shared pointer or only using packaged_tasks in a scheduler I'd love to hear it because I'm stumped...

I am pretty unsatisfied with this solution.... Any ideas? I am able to re-implement std::function with move symantics but this seems like a massive pain in the arse and it has its own problems regarding object lifetime (but they already exist when using std::function with reference capture).

Some examples of the problem:

EDIT Note in the target application I cannot do std::thread a (std::move(a)) as the scheduler threads are always running, at most they are put in a sleep state, never joined, never stopped. A fixed number of threads are in the thread pool, I cannot create threads for each task.

auto proms = std::make_unique<std::promise<int>>();
auto future = proms->get_future();

std::thread runner(std::move(std::function( [prom = std::move(proms)]() mutable noexcept
{
    prom->set_value(80085);
})));

std::cout << future.get() << std::endl;
std::cin.get();

And an example with a packaged_task

auto pack = std::packaged_task<int(void)>
(   [] 
    {   
        return 1; 
    });
auto future = pack.get_future();

std::thread runner(std::move(std::function( [pack = std::move(pack)]() mutable noexcept
{
    pack();
})));

std::cout << future.get() << std::endl;
std::cin.get();

EDIT

I need to do this from the context of a scheduler, I won't be able to move to the thread.

Please note that the above is minimum re-producible, std::async is not adequate for my application.

David Ledger
  • 2,033
  • 1
  • 12
  • 27
  • Possible duplicate of [How to create an std::function from a move-capturing lambda expression?](https://stackoverflow.com/questions/25421346/how-to-create-an-stdfunction-from-a-move-capturing-lambda-expression) – Nikita Kniazev Sep 01 '18 at 14:16
  • Perhaps a partial overlap, but this question is more broad, I'm asking about a potential solution or alternative in the context of a scheduler. – David Ledger Sep 02 '18 at 03:04
  • This approach seems to work well, https://codereview.stackexchange.com/questions/176431/unique-function-a-move-only-stdfunction-replacement – David Ledger Sep 02 '18 at 06:18

1 Answers1

2

The main question is: Why you want to wrap a lambda with std::function before passing it to the std::thread constructor?

It is perfectly fine to do this:

std::thread runner([prom = std::move(proms)]() mutable noexcept
{
    prom->set_value(80085);
});

You can find the explanation of why std::function does not allow you to store a move-only lambda here.

If you were going to pass std::function with wrapped lambda to some function, instead of:

void foo(std::function<void()> f)
{
    std::thread runner(std::move(f));
    /* ... */
}

foo(std::function<void()>([](){}));

You can do this:

void foo(std::thread runner)
{
    /* ... */
}

foo(std::thread([](){}));

Update: It can be done in an old-fashioned way.

std::thread runner([prom_deleter = proms.get_deleter(), prom = proms.release()]() mutable noexcept
{
    prom->set_value(80085);
    // if `proms` deleter is of a `default_deleter` type
    // the next line can be simplified to `delete prom;`
    prom_deleter(prom);
});
Nikita Kniazev
  • 3,728
  • 2
  • 16
  • 30
  • The focus is with a thread pool, the thread is constantly running consuming available functions. I cannot move to the thread in that situation, I wasn't very clear with that in the question sorry. – David Ledger Sep 02 '18 at 03:04
  • The tasks are put into a lock-free deque, and are part of a work stealing scheduler. What do you think about my suggestion of writing a move when copy version of unique_ptr? – David Ledger Sep 02 '18 at 03:12
  • To clarify, you have a container (queue) of `std::function`s and you want to store move-only lambdas in it? I would suggest you next time to be more precise, because your question contains a lot of unrelated stuff. – Nikita Kniazev Sep 02 '18 at 13:53
  • "a move when copy version of unique_ptr" this sounds like `auto_ptr`. I have a better idea, will update the answer soon. – Nikita Kniazev Sep 02 '18 at 14:07
  • Thats awesome! Testing tonight. – David Ledger Sep 04 '18 at 01:23
  • Be aware that your object will **not** be destructed in `std::function` destructor, only after function call, and second call of this function is forbidden. – Nikita Kniazev Sep 04 '18 at 11:50
  • Yeah I understand, that is desirable in this case :) – David Ledger Sep 04 '18 at 11:51