std::function and std::packaged_task conversion
it should be convertable to
std::function<void()>
, yes?
No. The relevant constructor of function
requires its argument to be CopyConstructible and packaged_task
is not CopyConstructible, it is only MoveConstructible, because its copy constructor and copy assignment operator are deleted. This is an unfortunate requirement of function
but necessary for function
to be copyable, due to using type erasure to abstract away the details of the wrapped callable object.
Until quite late in the process the C++0x draft didn't require CopyConstructible but it was added to the final C++11 standard by DR 1287 so it's my fault, sorry ;-) An earlier concept-enabled draft had required the CopyConstructible
concept, but that got lost when concepts were removed from the draft.
I had this exact problem today. When implementing a synchronous call in terms of an asynchronous service, the obvious thing to do is to try to store a packaged_task in a handler function so that the caller's future can be made ready when the asynchronous handler completes.
Unfortunately c++11 (and 14) don't allow this. Tracking it down cost me nearly a day of development time, and the process led me to this answer.
I knocked up a solution - a replacement for std::function with a specialisation for std::packaged_task.
Thanks both yngum and Jonathan for posting the question and the answer.
code:
// general template form
template<class Callable>
struct universal_call;
// partial specialisation to cover most cases
template<class R, class...Args>
struct universal_call<R(Args...)> {
template<class Callable>
universal_call(Callable&& callable)
: _impl { std::make_shared<model<Callable>>(std::forward<Callable>(callable)) }
{}
R operator()(Args&&...args) const {
return _impl->call(std::forward<Args>(args)...);
}
private:
struct concept {
virtual R call(Args&&...args) = 0;
virtual ~concept() = default;
};
template<class Callable>
struct model : concept {
model(Callable&& callable)
: _callable(std::move(callable))
{}
R call(Args&&...args) override {
return _callable(std::forward<Args>(args)...);
}
Callable _callable;
};
std::shared_ptr<concept> _impl;
};
// pathalogical specialisation for std::packaged_task -
// erases the return type from the signature
template<class R, class...Args>
struct universal_call<std::packaged_task<R(Args...)>>
: universal_call<void(Args...)>
{
using universal_call<void(Args...)>::universal_call;
};
// (possibly) helpful function
template<class F>
universal_call<F> make_universal_call(F&& f)
{
return universal_call<F>(std::forward<F>(f));
}