packaged_task<R1(Args1...)> -> packaged_task<R2(Args2)>

46 views
Skip to first unread message

Mutz, Marc

unread,
Apr 27, 2018, 7:39:19 AM4/27/18
to Standard Proposals
Hi,

Currently, when you construct a std::packaged_task from another one with
compatible signature, you get two independent shared states (ie. two
futures), because the wrapping task contains the wrapped task as if it
was any other callable.

The ship has sailed for implicit conversions, but as I myself have found
and these SO questions suggest:

https://stackoverflow.com/questions/31072279/implementing-a-simple-generic-thread-pool-in-c11

https://stackoverflow.com/questions/28179817/how-can-i-store-generic-packaged-tasks-in-a-container

There is a hole in the standard library, since while you can always
construct a std::function with a compatible signature in the first
place, this does not work for packaged_task, since the return type of
the signature determines the type of future returned from get_future().

What these SO users need, and I needed, too, recently, was a
std::packaged_task<void()> for type erasure constructed from a
packaged_task<R()>, on which get_future() was called. Alternatively,
they could use a unique_function, which, however, is also lacking in the
standard.

Now, instead of cluttering the API of packaged_task, I imagine that a
simple cast function could do the job:

fun: some callable compatible with R()
m_tasks: a container of packaged_task<void()>

auto task = std::packaged_task<R()>{fun};
auto result = task.get_future();
m_tasks.push_back(packaged_task_cast<void()>(std::move(task)));

I deliberately didn't look into how to implement this, yet, as I wanted
to retain the API user's view for now.

Alternatively, the future and the packaged_task objects could be created
together, with a make_ function. This could also supply the much-missed
feature of binding arguments to the invocation, much like
make_shared/unique allow to pass arguments to the ctor while the
shared_ptr/unique_ptr ctors only take pointers:

auto r = std::make_packaged_task_and_future<void()>(fun, args...);
m_tasks.push_back(std::move(r.task)); // a packaged_task<void()>,
since this is what we asked for
return std::move(r.future); // a future<R>, deduced from the return
type of 'fun'

Opinions?

Thanks,
Marc

Arthur O'Dwyer

unread,
Apr 27, 2018, 7:12:43 PM4/27/18
to ISO C++ Standard - Future Proposals
On Friday, April 27, 2018 at 4:39:19 AM UTC-7, Mutz, Marc wrote:
Hi,

Currently, when you construct a std::packaged_task from another one with
compatible signature, you get two independent shared states (ie. two
futures), because the wrapping task contains the wrapped task as if it
was any other callable.

The ship has sailed for implicit conversions [...]
What these SO users need, and I needed, too, recently, was a
std::packaged_task<void()> for type erasure constructed from a
packaged_task<R()>, on which get_future() was called.

This topic has been discussed in here before:

Personally, I think that it would be safe to recall the ship on implicit conversions and just make

    packaged_task<T()> pt1 = ...;
    packaged_task<void()> pt2 = std::move(pt1);

do the right thing. Yes it would be a quietly breaking change (as discussed in the prior thread linked above), but it would break only stupid code, AFAICT. I would be interested to see any non-stupid examples of code that would break.


On a different topic:
I agree that the correct API for promise/future factories is to return a `pair<promise, future>`, or in this case a `pair<packaged_task, future>`. That seems orthogonal to the rest of your proposal, though, and is super easy to implement with a "user-land" helper function.


On a different topic:
   auto r = std::make_packaged_task_and_future<void()>(fun, args...);

This style of passing "args to bind" to a task/thread went out of style in C++11 with the introduction of lambdas. If you want to "bind" some args to `fun`, then you should write

    auto r = std::make_packaged_task_and_future<void()>(
        [fun, args...]() { fun(args...); }
    );

which allows you to control the capture-mode of `fun` and `args...` completely, with no need to fiddle around with `std::reference_wrapper` and no need to worry about thinkos leading to potentially dangling references.

HTH,
Arthur
Reply all
Reply to author
Forward
0 new messages