4

I was naively expecting this to compile:

template <typename Func>
auto run(Func && func) {
    auto package = std::packaged_task{std::forward<Func>(func)}; // deduce the template args automatically (C++17)
    auto future = package.get_future();
    enqueue(std::packaged_task<void()>{std::move(package)}); // only works if packaged_task is <R()>, but ok
    return future;
}

For exposition: this might be from a thread-pool implementation, enqueue() just queues the argument for execution on the worker threads.

The thing is, however, that there are no deduction guides for packaged_task, so C++17 constructor template argument deduction fails, of course.

So, why are there no deduction guides?

Marc Mutz - mmutz
  • 24,485
  • 12
  • 80
  • 90

1 Answers1

2

None of the three std::future factories have deduction guides

  • std::async is a function template, so doesn't need any
  • std::promise would have to deduce from (), which is ambiguous
  • std::packaged_task would have to deduce from potentially overloaded functions (and operator()s), which is ambiguous

Note that packaged_task is the wrong future-factory if you have the arguments to your callable, you only supply the callable to it's constructor. Your example code probably wants to be auto future = std::async(std::forward<Args>(args)...);

Your example should really be something like

template <typename> struct function_traits;

template <typename Ret, typename...Args>
struct function_traits<std::function<Ret(Args...)>
{
    using type = Ret(Args...);
}

template <typename Sig> 
using function_traits_t = function_traits<Sig>::type;

template <typename F>
auto run(F&& f) {
    using Sig = function_traits_t<decltype(std::function{f})>;
    auto package = std::packaged_task<Sig>{std::forward<F>(f)}; // fails to deduce the template args automatically
    auto future = package.get_future();
    enqueue(std::move(package)); // function can deduce
    return future;
}
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • 3
    `std::function` has deduction guides, why not `std::packaged_task`? – Barry Apr 19 '18 at 15:08
  • I believe you misunderstand what `std::async` does. It does _not_ create a `packaged_task`, which wraps its arguments for later execution by `operator()`, it spawns a _new_ thread. The whole point of a thread-pool is, however, to amortize the thread-creation costs over a large number of tasks. – Marc Mutz - mmutz Apr 19 '18 at 16:21
  • 2
    @MarcMutz-mmutz No, I'm pointing out OP may be confused as to what `packaged_task` does. It is not passed arguments until it is ready to be called – Caleth Apr 19 '18 at 16:24
  • @Caleth indeed, OP was :) Fixed! Thanks! – Marc Mutz - mmutz Apr 19 '18 at 16:29
  • If I only support `R()`-style callables, I guess I can just `using R = decltype(std::declval()())`? – Marc Mutz - mmutz Apr 19 '18 at 16:54
  • 1
    Apparently it should have deduction guides according to https://en.cppreference.com/w/cpp/thread/packaged_task/deduction_guides since C++17. However, their example doesn't work on `gcc 11.2.0` with `-std=gnu++20`. – raldone01 Apr 25 '22 at 13:07