Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::function and std::packaged_task conversion

Tags:

c++

c++11

clang

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.
like image 432
yngccc Avatar asked Jun 03 '13 04:06

yngccc


2 Answers

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.

like image 77
Jonathan Wakely Avatar answered Oct 12 '22 12:10

Jonathan Wakely


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));
}
like image 42
Richard Hodges Avatar answered Oct 12 '22 11:10

Richard Hodges