This will take some work.
First, write task<Sig>
. task<Sig>
is a std::function
that only expects its argument to be movable, not copyable.
Your internal type Messages
are going to be task<void()>
. So you can be lazy and have your task
only support nullary functions if you like.
Second, send creates a std::packaged_task<R> package(f);
. It then gets the future out of the task, and then moves the package
into your queue of messages. (This is why you need a move-only std::function
, because packaged_task
can only be moved).
You then return the future
from the packaged_task
.
template<class F, class R=std::result_of_t<F const&()>>
std::future<R> send(F&& f) {
packaged_task<R> package(std::forward<F>(f));
auto ret = package.get_future();
mq.push_back( std::move(package) );
return ret;
}
clients can grab ahold of the std::future
and use it to (later) get the result of the call back.
Amusingly, you can write a really simple move-only nullary task as follows:
template<class R>
struct task {
std::packaged_task<R> state;
template<class F>
task( F&& f ):state(std::forward<F>(f)) {}
R operator()() const {
auto fut = state.get_future();
state();
return f.get();
}
};
but that is ridiculously inefficient (packaged task has synchronization stuff in it), and probably needs some cleanup. I find it amusing because it uses a packaged_task
for the move-only std::function
part.
Personally, I've run into enough reasons to want move-only tasks (among this problem) to feel that a move-only std::function
is worth writing. What follows is one such implementation. It isn't heavily optimized (probably about as fast as most std::function
however), and not debugged, but the design is sound:
template<class Sig>
struct task;
namespace details_task {
template<class Sig>
struct ipimpl;
template<class R, class...Args>
struct ipimpl<R(Args...)> {
virtual ~ipimpl() {}
virtual R invoke(Args&&...args) const = 0;
};
template<class Sig, class F>
struct pimpl;
template<class R, class...Args, class F>
struct pimpl<R(Args...), F>:ipimpl<R(Args...)> {
F f;
R invoke(Args&&...args) const final override {
return f(std::forward<Args>(args)...);
};
};
// void case, we don't care about what f returns:
template<class...Args, class F>
struct pimpl<void(Args...), F>:ipimpl<void(Args...)> {
F f;
template<class Fin>
pimpl(Fin&&fin):f(std::forward<Fin>(fin)){}
void invoke(Args&&...args) const final override {
f(std::forward<Args>(args)...);
};
};
}
template<class R, class...Args>
struct task<R(Args...)> {
std::unique_ptr< details_task::ipimpl<R(Args...)> > pimpl;
task(task&&)=default;
task&operator=(task&&)=default;
task()=default;
explicit operator bool() const { return static_cast<bool>(pimpl); }
R operator()(Args...args) const {
return pimpl->invoke(std::forward<Args>(args)...);
}
// if we can be called with the signature, use this:
template<class F, class=std::enable_if_t<
std::is_convertible<std::result_of_t<F const&(Args...)>,R>{}
>>
task(F&& f):task(std::forward<F>(f), std::is_convertible<F&,bool>{}) {}
// the case where we are a void return type, we don't
// care what the return type of F is, just that we can call it:
template<class F, class R2=R, class=std::result_of_t<F const&(Args...)>,
class=std::enable_if_t<std::is_same<R2, void>{}>
>
task(F&& f):task(std::forward<F>(f), std::is_convertible<F&,bool>{}) {}
// this helps with overload resolution in some cases:
task( R(*pf)(Args...) ):task(pf, std::true_type{}) {}
// = nullptr support:
task( std::nullptr_t ):task() {}
private:
// build a pimpl from F. All ctors get here, or to task() eventually:
template<class F>
task( F&& f, std::false_type /* needs a test? No! */ ):
pimpl( new details_task::pimpl<R(Args...), std::decay_t<F>>{ std::forward<F>(f) } )
{}
// cast incoming to bool, if it works, construct, otherwise
// we should be empty:
// move-constructs, because we need to run-time dispatch between two ctors.
// if we pass the test, dispatch to task(?, false_type) (no test needed)
// if we fail the test, dispatch to task() (empty task).
template<class F>
task( F&& f, std::true_type /* needs a test? Yes! */ ):
task( f?task( std::forward<F>(f), std::false_type{} ):task() )
{}
};
live example.
is a first sketch at a library-class move-only task object. It also uses some C++14 stuff (the std::blah_t
aliases) -- replace std::enable_if_t<???>
with typename std::enable_if<???>::type
if you are a C++11-only compiler.
Note that the void
return type trick contains some marginally questionable template overload tricks. (It is arguable if it is legal under the wording of the standard, but every C++11 compiler will accept it, and it is likely to become legal if it is not).
std::async
launches new threads or uses a thread pool. I would like to queue tasks on a single thread for sequential execution. – ChopperActive
holding astd::unique_ptr<std::thread>
instead of just a plainstd::thread
? – Attired