I'm attempting to make a threadpool that takes any kind of function, and returns a future for any return value/exceptions that function may have. I'm doing this largely as an exercise to learn modern threading and some template programming. I tried basing my syntax loosely off how MSVC does std::function and futures.
Here's a minimum snippet of where my problem is:
#include <functional>
#include <future>
#include <utility>
#include <queue>
#include <memory>
using Job = std::function<void()>;
std::queue<std::unique_ptr<Job>> queue;
template<typename FuncType, typename... Args>
auto add(FuncType&& func, Args&&... args) ->std::future<decltype(func)(decltype(args)...)> {
auto task = std::packaged_task<decltype(func)(decltype(args)...)>(std::bind (std::forward<FuncType>(func), std::forward<Args>(args)...));
auto future = task.get_future();
queue.push(std::make_unique<Job>([task]() { task(); }));
return future;
}
void voidFunc(){};
int main()
{
add(voidFunc);
}
This fails to compile with the errors:
/usr/include/c++/4.9/future: In instantiation of 'class std::future<void (&())()>':
28:17: required from here
/usr/include/c++/4.9/future:697:7: error: function returning a function
get()
^
In instantiation of 'add(FuncType&&, Args&& ...)::<lambda()> [with FuncType = void (&)(); Args = {}]':
19:37: required from 'struct add(FuncType&&, Args&& ...) [with FuncType = void (&)(); Args = {}; decltype (func) = void (&)()]::<lambda()>'
19:56: required from 'std::future<decltype (func)(decltype (args)...)> add(FuncType&&, Args&& ...) [with FuncType = void (&)(); Args = {}; decltype (func) = void (&)()]'
28:17: required from here
19:52: error: passing 'const std::packaged_task<void (&())()>' as 'this' argument of 'void std::packaged_task<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) [with _Res = void (&)(); _ArgTypes = {}]' discards qualifiers [-fpermissive]
In instantiation of 'std::future<decltype (func)(decltype (args)...)> add(FuncType&&, Args&& ...) [with FuncType = void (&)(); Args = {}; decltype (func) = void (&)()]':
28:17: required from here
19:36: error: use of deleted function 'std::packaged_task<_Res(_ArgTypes ...)>::packaged_task(const std::packaged_task<_Res(_ArgTypes ...)>&) [with _Res = void (&)(); _ArgTypes = {}]'
In file included from 5:0:
/usr/include/c++/4.9/future:1413:7: note: declared here
packaged_task(const packaged_task&) = delete;
^
21:10: error: could not convert 'future' from 'std::future<void (&)()>' to 'std::future<void (&())()>'
In file included from 5:0:
/usr/include/c++/4.9/future: In instantiation of 'static std::__future_base::_Task_setter<_Res_ptr> std::__future_base::_S_task_setter(_Res_ptr&, _BoundFn&&) [with _Res_ptr = std::unique_ptr<std::__future_base::_Result<void (&)()>, std::__future_base::_Result_base::_Deleter>; _BoundFn = std::_Bind_simple<std::reference_wrapper<std::_Bind<void (*())()> >()>; typename _Res_ptr::element_type::result_type = void (&)()]':
/usr/include/c++/4.9/future:1318:70: required from 'void std::__future_base::_Task_state<_Fn, _Alloc, _Res(_Args ...)>::_M_run(_Args ...) [with _Fn = std::_Bind<void (*())()>; _Alloc = std::allocator<int>; _Res = void (&)(); _Args = {}]'
29:1: required from here
/usr/include/c++/4.9/future:539:57: error: could not convert 'std::ref(_Tp&) [with _Tp = std::_Bind_simple<std::reference_wrapper<std::_Bind<void (*())()> >()>]()' from 'std::reference_wrapper<std::_Bind_simple<std::reference_wrapper<std::_Bind<void (*())()> >()> >' to 'std::function<void (&())()>'
return _Task_setter<_Res_ptr>{ __ptr, std::ref(__call) };
^
In file included from /usr/include/c++/4.9/memory:81:0,
from /usr/include/c++/4.9/thread:40,
from /usr/include/c++/4.9/future:40,
from 5:
/usr/include/c++/4.9/bits/unique_ptr.h:764:5: error: 'typename std::_MakeUniq<_Tp>::__single_object std::make_unique(_Args&& ...) [with _Tp = std::function<void()>; _Args = {add(FuncType&&, Args&& ...) [with FuncType = void (&)(); Args = {}; decltype (func) = void (&)()]::<lambda()>}; typename std::_MakeUniq<_Tp>::__single_object = std::unique_ptr<std::function<void()> >]', declared using local type 'add(FuncType&&, Args&& ...) [with FuncType = void (&)(); Args = {}; decltype (func) = void (&)()]::<lambda()>', is used but never defined [-fpermissive]
make_unique(_Args&&... __args)
^
In function 'std::future<decltype (func)(decltype (args)...)> add(FuncType&&, Args&& ...) [with FuncType = void (&)(); Args = {}; decltype (func) = void (&)()]':
22:2: warning: control reaches end of non-void function [-Wreturn-type]
I think I have two problems here: I don't know how to properly use decltype to get the right function signature (I've also looked into invoke_result, but I wasn't having any luck there either), and I think I'm probably also not passing the packaged task to the queue properly.
How do I get the right function signature for the future and packaged task, and how do I properly pass the packaged task to a std::function on a queue (that will later be grabbed by another thread)?
Two problems:
1) The signature of packaged task should be
std::packaged_task<std::invoke_result_t<Func&&,Args&&...>(Args&&...)>
. This uses invoke_result_t
to compute the return type, but also passes the argument types to the packaged task.
2) Bigger problem: std::function
requires that the function be copy constructable, which std::packaged_task
is not. You'll have to create your own queue to hold the packaged tasks. I've implemented it before using a base class Task with a templated derived class to hold the packaged task.