I am trying to move a std::packaged_task
into a std::vector
of std::function<void()>
, because std::packaged_task
has void operator()( ArgTypes... args )
overloaded, it should be convertable to std::function<void()>
, yes?
This doesnt compile both on MSVC and Clang, MSVC complains about cannot convert void to int, clang complains deleted copy constructor for std::packaged_task
, shouldn't move version of std::vector::push_back
be called here? what is going on, is this a bug?
int main ()
{
std::vector<std::function<void()>> vec;
std::packaged_task<int()> task( [] { return 100; } );
vec.push_back( std::move(task) );
}
Here is the cryptic template error messages for clang
In file included from main.cpp:1:
In file included from /usr/bin/../lib/c++/v1/iostream:38:
In file included from /usr/bin/../lib/c++/v1/ios:216:
In file included from /usr/bin/../lib/c++/v1/__locale:15:
In file included from /usr/bin/../lib/c++/v1/string:434:
In file included from /usr/bin/../lib/c++/v1/algorithm:594:
/usr/bin/../lib/c++/v1/memory:2236:15: error: call to deleted constructor of
'std::__1::packaged_task<int ()>'
__first_(_VSTD::forward<_Args1>(get<_I1>(__first_args))...)
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/c++/v1/memory:2414:15: note: in instantiation of function
template specialization
'std::__1::__libcpp_compressed_pair_imp<std::__1::packaged_task<int ()>,
std::__1::allocator<std::__1::packaged_task<int ()> >,
2>::__libcpp_compressed_pair_imp<const std::__1::packaged_task<int ()> &,
const std::__1::allocator<std::__1::packaged_task<int ()> > &, 0, 0>'
requested here
: base(__pc, _VSTD::move(__first_args), _VSTD::move(__second_args),
^
/usr/bin/../lib/c++/v1/functional:996:11: note: in instantiation of function
template specialization
'std::__1::__compressed_pair<std::__1::packaged_task<int ()>,
std::__1::allocator<std::__1::packaged_task<int ()> >
>::__compressed_pair<const std::__1::packaged_task<int ()> &, const
std::__1::allocator<std::__1::packaged_task<int ()> > &>' requested here
: __f_(piecewise_construct, _VSTD::forward_as_tuple(__f),
^
/usr/bin/../lib/c++/v1/functional:1035:17: note: in instantiation of member
function 'std::__1::__function::__func<std::__1::packaged_task<int ()>,
std::__1::allocator<std::__1::packaged_task<int ()> >, void ()>::__func'
requested here
::new (__p) __func(__f_.first(), __f_.second());
^
/usr/bin/../lib/c++/v1/functional:1277:26: note: in instantiation of member
function 'std::__1::__function::__func<std::__1::packaged_task<int ()>,
std::__1::allocator<std::__1::packaged_task<int ()> >, void ()>::__clone'
requested here
::new (__f_) _FF(_VSTD::move(__f));
^
/usr/bin/../lib/c++/v1/memory:1681:31: note: in instantiation of function
template specialization 'std::__1::function<void
()>::function<std::__1::packaged_task<int ()> >' requested here
::new((void*)__p) _Up(_VSTD::forward<_Args>(__args)...);
^
/usr/bin/../lib/c++/v1/memory:1608:18: note: in instantiation of function
template specialization 'std::__1::allocator<std::__1::function<void ()>
>::construct<std::__1::function<void ()>, std::__1::packaged_task<int ()>
>' requested here
{__a.construct(__p, _VSTD::forward<_Args>(__args)...);}
^
/usr/bin/../lib/c++/v1/memory:1492:14: note: in instantiation of function
template specialization
'std::__1::allocator_traits<std::__1::allocator<std::__1::function<void
()> > >::__construct<std::__1::function<void ()>,
std::__1::packaged_task<int ()> >' requested here
{__construct(__has_construct<allocator_type, pointer, _Args...>(),
^
/usr/bin/../lib/c++/v1/vector:1519:25: note: in instantiation of function
template specialization
'std::__1::allocator_traits<std::__1::allocator<std::__1::function<void
()> > >::construct<std::__1::function<void ()>,
std::__1::packaged_task<int ()> >' requested here
__alloc_traits::construct(this->__alloc(),
^
main.cpp:19:6: note: in instantiation of function template specialization
'std::__1::vector<std::__1::function<void ()>,
std::__1::allocator<std::__1::function<void ()> >
>::emplace_back<std::__1::packaged_task<int ()> >' requested here
vec.emplace_back( std::move(task) );
^
/usr/bin/../lib/c++/v1/future:1956:5: note: function has been explicitly marked
deleted here
packaged_task(const packaged_task&) = delete;
^
2 errors generated.
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));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With