0

I need a bit of help. I need to perfect forward a tuple in a specific way. Imagine this

template <typename F, typename... Args>
auto package_task(F&& func, Args&&... args) -> std::function<void()>
{
    //for the purposes of this example imagine capture to be std::move and
    //to be inplace in the lambdas' capture.
    auto callable = capture(std::forward<F>(func));
    auto params = capture(std::make_tuple(std::forward<Args>(args)...));

    return [callable , params]() mutable { std::apply(callable, params); };
}

the task that is packaged will be executed later on a different thread but when I call apply I need the "callable" to have it's params expanded from the tuple and forwarded exactly like they were passed in the package_task function. I can't use forward_as_tuple since I am copying/moving the arguments and the callable to be executed later. I need something like

template <typename F, typename... Args>
auto package_task(F&& func, Args&&... args) -> std::function<void()>
{
    //for the purposes of this example image capture to be std::move and
    //to be inplace in the lambdas' capture.
    auto callable = capture(std::forward<F>(func));
    auto params = capture(std::make_tuple(std::forward<Args>(args)...));

    return [callable , params]() mutable { std::apply_but_expand_as<Args&&…>(callable, params); };
}

Any ideas would be appreciated.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Looks like you're rewriting `bind`, you could check how standard bind or boost bind does it. – tkausl Sep 25 '18 at 18:56
  • well capture is actualy a move_on_copy wrapper that does std::move when copy constructor is invoked. This allows me to work with std::function and move-only objects that I want to capture. – Nikolai Petrov Ivanov Sep 25 '18 at 19:37

1 Answers1

2

If you're trying to make a one-shot callable that forwards its arguments through, the best you can do with C++17 or earlier is:

template <typename F, typename... Args>
auto package_task(F&& func, Args&&... args) -> std::function<void()>
{
    return [func = std::forward<F>(func),
            args_tuple = std::make_tuple(std::forward<Args>(args)...)]
           () mutable
    {
        return std::apply([](auto&&... args){
            return std::invoke(
                std::forward<F>(func),
                std::forward<Args>(args)...
                );
        }, args_tuple);
    };
}

That is, you forward the Args into a tuple, apply the tuple and then forward them back out.


In C++20, thanks to P0780, this can be simplified to:

template <typename F, typename... Args>
auto package_task(F&& func, Args&&... args) -> std::function<void()>
{
    return [func = std::forward<F>(func),
            ...args = std::forward<Args>(args)] () mutable
    {
        return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
    };
}
Barry
  • 286,269
  • 29
  • 621
  • 977
  • well this kinda works great on gcc. Msvc 2015/2017 can't really handle the expansion inside the generic lambda and gives me this. error: C3520: 'Args': parameter pack must be expanded in this context. I have no idea how to make it work there though. For now I can get away with a std::move instead of the std::forward in the invoke call since the lambda owns the data and it works on msvc as well – Nikolai Petrov Ivanov Sep 25 '18 at 21:12
  • This works well with gcc/clang/mingw except msvc. Any ideas how to work around that error I mentioned above with msvc? – Nikolai Petrov Ivanov Sep 26 '18 at 19:30
  • i guess the auto&& trips the msvc compiler, replacing it with std::decay_t& fixed it – Nikolai Petrov Ivanov Sep 26 '18 at 20:06