c++tuplesc++14perfect-forwardingstdapply

Perferct forward a copied std::tuple


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.


Solution

  • 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)...);
        };
    }