4

I have this minimal not-working example of code

#include <future>

int main()
{
    auto intTask = std::packaged_task<int()>( []()->int{ return 5; } );
    std::packaged_task<void()> voidTask{ std::move(intTask) };
}

Why doesn't it compile (on gcc 4.8.1)? I suspect, the reason is, that std::packaged_task stores the lambda internally inside an std::function which needs a CopyConstructible argument. However, std::packaged_task is move-only. Is this a bug? What does the standard say about it? In my opinion std::packaged_task should not need a CopyConstructible argument, but a MoveConstructible argument should be enough.

By the way, when I replace std::packaged_task<int()> by std::packaged_task<void()> everything compiles fine.

GCC 4.8.1 is giving me this error message:

In file included from /usr/include/c++/4.6/future:38:0,
                 from ../cpp11test/main.cpp:160:
/usr/include/c++/4.6/functional: In static member function 'static void        std::_Function_base::_Base_manager<_Functor>::_M_clone(std::_Any_data&, const std::_Any_data&, std::false_type) [with _Functor = std::packaged_task<int()>, std::false_type = std::integral_constant<bool, false>]':
/usr/include/c++/4.6/functional:1652:8:   instantiated from 'static bool std::_Function_base::_Base_manager<_Functor>::_M_manager(std::_Any_data&, const std::_Any_data&, std::_Manager_operation) [with _Functor = std::packaged_task<int()>]'
/usr/include/c++/4.6/functional:2149:6:   instantiated from 'std::function<_Res(_ArgTypes ...)>::function(_Functor, typename std::enable_if<(! std::is_integral<_Functor>::value), std::function<_Res(_ArgTypes ...)>::_Useless>::type) [with _Functor = std::packaged_task<int()>, _Res = void, _ArgTypes = {}, typename std::enable_if<(! std::is_integral<_Functor>::value), std::function<_Res(_ArgTypes ...)>::_Useless>::type = std::function<void()>::_Useless]'
/usr/include/c++/4.6/bits/shared_ptr_base.h:410:4:   instantiated from 'std::_Sp_counted_ptr_inplace<_Tp, _Alloc, _Lp>::_Sp_counted_ptr_inplace(_Alloc, _Args&& ...) [with _Args = {std::packaged_task<int()>}, _Tp = std::__future_base::_Task_state<void()>, _Alloc = std::allocator<std::__future_base::_Task_state<void()> >, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]'
/usr/include/c++/4.6/bits/shared_ptr_base.h:518:8:   instantiated from 'std::__shared_count<_Lp>::__shared_count(std::_Sp_make_shared_tag, _Tp*, const _Alloc&, _Args&& ...) [with _Tp = std::__future_base::_Task_state<void()>, _Alloc = std::allocator<std::__future_base::_Task_state<void()> >, _Args = {std::packaged_task<int()>}, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]'
/usr/include/c++/4.6/bits/shared_ptr_base.h:987:35:   instantiated from 'std::__shared_ptr<_Tp, _Lp>::__shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = std::allocator<std::__future_base::_Task_state<void()> >, _Args = {std::packaged_task<int()>}, _Tp = std::__future_base::_Task_state<void()>, __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]'
/usr/include/c++/4.6/bits/shared_ptr.h:317:64:   instantiated from 'std::shared_ptr<_Tp>::shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...) [with _Alloc = std::allocator<std::__future_base::_Task_state<void()> >, _Args = {std::packaged_task<int()>}, _Tp = std::__future_base::_Task_state<void()>]'
/usr/include/c++/4.6/bits/shared_ptr.h:535:39:   instantiated from 'std::shared_ptr<_Tp> std::allocate_shared(const _Alloc&, _Args&& ...) [with _Tp = std::__future_base::_Task_state<void()>, _Alloc = std::allocator<std::__future_base::_Task_state<void()> >, _Args = {std::packaged_task<int()>}]'
/usr/include/c++/4.6/bits/shared_ptr.h:551:42:   instantiated from 'std::shared_ptr<_Tp1> std::make_shared(_Args&& ...) [with _Tp = std::__future_base::_Task_state<void()>, _Args = {std::packaged_task<int()>}]'
/usr/include/c++/4.6/future:1223:66:   instantiated from 'std::packaged_task<_Res(_ArgTypes ...)>::packaged_task(_Fn&&) [with _Fn = std::packaged_task<int()>, _Res = void, _ArgTypes = {}]'
../cpp11test/main.cpp:165:61:   instantiated from here
/usr/include/c++/4.6/functional:1616:4: error: use of deleted function 'std::packaged_task<_Res(_ArgTypes ...)>::packaged_task(std::packaged_task<_Res(_ArgTypes ...)>&) [with _Res = int, _ArgTypes = {}, std::packaged_task<_Res(_ArgTypes ...)> = std::packaged_task<int()>]'
/usr/include/c++/4.6/future:1244:7: error: declared here

UPDATE: I have written the following test program. It seems to support the assumption that the reason is missing CopyConstructability. Again, what are the requirements on the type of the object from which an std::packaged_task may be constructed?

#include <future>

struct Functor {
    Functor() {}
    Functor( const Functor & ) {} // without this line it doesn't compile
    Functor( Functor && ) {}
    int operator()(){ return 5; }
};

int main() {
    auto intTask = std::packaged_task<int()>( Functor{} );
}
Ralph Tandetzky
  • 22,780
  • 11
  • 73
  • 120
  • I revised my answer. You're falling over the `explicit` constructor. – Kerrek SB Jul 05 '13 at 17:27
  • Why are you trying to move a std::packaged_task into a std::packaged_task? – Vaughn Cato Jul 05 '13 at 17:27
  • Actually I want to push `std::packaged_task` for any type `T` on a queue of `std::packaged_task`. I need a common type for the queue elements. The goal is to implement a thread pool which internally holds a concurrent queue of tasks. `std::function` will not be enough because I want to return `std::futures` for the results of the calculations. – Ralph Tandetzky Jul 05 '13 at 17:30
  • That sounds like a completely different question, and *should be* a completely different question. – Kerrek SB Jul 05 '13 at 18:17
  • I wanted to limit the scope of my question so I don't have to explain the entire problem. This question is where I got stuck. That's why I posted it. – Ralph Tandetzky Jul 05 '13 at 18:23

3 Answers3

2

Indeed, packaged_task only has a moving constructor (30.6.9/2):

template <class F> explicit packaged_task(F&& f);

However, your problem is the explicit constructor. So write it like this:

std::packaged_task<int()> pt([]() -> int { return 1; });

Complete example:

#include <future>
#include <thread>

int main()
{
    std::packaged_task<int()> intTask([]() -> int { return 5; } );
    auto f = intTask.get_future();
    std::thread(std::move(intTask)).detach();
    return f.get();
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • This doesn't solve the problem. The code fails to compile due to the second line in the `main()` function. I want to move a `std::packaged_task` into a `std::packaged_task`. I explained the reason for it in the comments to my question. – Ralph Tandetzky Jul 05 '13 at 17:34
0

No. You just can't move a packaged_task<int ()> into a packaged_task<void ()>. These types a unrelated and cannot be move-assigned or move-constructed from each other. If you for some reason really want to do that you can "swallow" the result of the int () like this

Paul Michalik
  • 4,331
  • 16
  • 18
  • Unfortunately the solution in your link doesn't work for me, since the `intTask` will go out of scope before the `voidTask` is executed in my problem. The `[&]` capture is not good. But a `[=]` will also not do, because the `std::packaged_task` is not `Copyable`. Maybe a `std::shared_ptr` solution could work. – Ralph Tandetzky Jul 05 '13 at 17:42
  • You simply cannot do that, the types are not compatible. You will need to provide a level of indirection which wraps both types into an instance of a common type. The return type of this new "task" type would need to be interrogated for the real run-time type. – Paul Michalik Jul 05 '13 at 19:26
0

The standard (as of N3690) doesn't state anything explicitly about the requirements of the type F in

template <class R, class... ArgTypes>
template <class F>
packaged_task<R(ArgTypes...)>::packaged_task(F&& f);

(see 30.6.9.1) However, it states that

Invoking a copy of f shall behave the same as invoking f.

and that this call can throw

any exceptions thrown by the copy or move constructor of f, or std::bad_alloc if memory for the internal data structures could not be allocated.

This implicitly implies that the type F must be at least MoveConstructible, or CopyConstructible, if an lvalue reference is handed to the function.

Hence, it's not a bug, it's just not specified that precisely. To solve the problem of putting a std::packaged_task<int()> into a std::packaged_task<void()> just wrap the first into a shared_ptr like this:

#include <future>
#include <memory>

int main()
{
    auto intTask = std::make_shared<std::packaged_task<int()>>( 
        []()->int{ return 5; } );
    std::packaged_task<void()> voidTask{ [=]{ (*intTask)(); } };
}
Ralph Tandetzky
  • 22,780
  • 11
  • 73
  • 120