Search code examples
c++variadic-templatesrvalue-referenceperfect-forwarding

Problem with storing forward references(universal) to be forward and used later


Problem with storing forward references(universal) to be forward and used later

I'm writing a class where I can pass any function pointer with its argument types and return type, and later call the .Launch() method and forward the parameters for invoking the function later. I'm having problem making it accepting any type of parameters with forward references.

I'm using std::tuple to save to parameters and std::function to save the function pointer, std::apply to invoke the function with saved parameters in tuple. This is by the way simplified version of the class.

#include <functional>
#include <tuple>
#include <vector>

template <typename R, typename... Args>
class AnyWorker
{
public:
    AnyWorker(std::function<R(Args...)>&& in_pFunction)
        :pFunction{ in_pFunction }
    {
    }
    //why not "Args&&..." here?
    R Launch(Args... inArgs)
    {
        savedArgs = std::make_tuple(std::forward<Args>(inArgs)...);
        return Work();
    }

private:
    R Work()
    {
        return std::apply(pFunction, savedArgs);
    }

private:
    std::function<R(Args...)> pFunction;
    std::tuple<Args...> savedArgs;
};


int modifyVec1(std::vector<int> vec)
{
    int add{ 1 };
    for (int i{ 0 }; i < vec.size(); ++i)
        vec[i] += add;

    return 678;
}

std::vector<int> modifyVec2(std::vector<int>&& vec)
{
    std::vector<int> workingVec{vec};
    int add{ 1 };
    for (int i{ 0 }; i < vec.size(); ++i)
        workingVec[i] += add;

    return workingVec;
}

int modifyVec3(std::vector<int>* vec)
{
    int add{ 1 };
    for (int i{ 0 }; i < vec->size(); ++i)
        (*vec)[i] += add;

    return 985;
}

int modifyVec4(std::vector<int>& vec)
{
    int add{ 1 };
    for (int i{ 0 }; i < vec.size(); ++i)
        vec[i] += add;

    return 722;
}


int main()
{
    std::vector<int> vec{ 1, 2, 3 };
    std::vector<int> vec2{ 10, 20, 30 };
    std::vector<int> vec3;
    std::vector<int> vec4;
    //-----------------why not "std::vector<int>&&" here?
    AnyWorker<std::vector<int>, std::vector<int>> myWorker(modifyVec2);
    vec3 = std::move(myWorker.Launch(std::move(vec)));
    vec4 = std::move(myWorker.Launch(std::move(vec2)));


    AnyWorker<int, std::vector<int>*> myWorker2(modifyVec3);
    auto result = myWorker2.Launch(&vec3);
    result = myWorker2.Launch(&vec4);


    AnyWorker<int, std::vector<int>> myWorker3(modifyVec1);
    auto result2 = myWorker3.Launch(vec3);
    result2 = myWorker3.Launch(vec4);

    //AnyWorker<int, std::vector<int>&> myWorker4(modifyVec4);
    //auto result3 = myWorker4.Launch(vec);
    //result3 = myWorker4.Launch(vec2);   
}

Edit: I have managed to make it work with rvalue references and pointer and simple copy values but couldn't do it with lvalue reference. I also don't realize why I can't use forwarding references in variadic template .launch() if do use it will work for rvalue references and pointers but not simple copy values.

While trying to compile the with the modifyVec4 function I get the error : 'std::tuple<std::vector<int,std::allocator<int>> &>::tuple': no appropriate default constructor available

I have also checked this post Storing variable arguments to be forwarded later but I can't seem to find where I am doing wrong with forward references.


Solution

  • Actually the problem is very easy and fundamental, (lvalue) references must be initialized when declared, be it a single reference or a pack of references. And storing them works just like you store a reference with some other reference. (int a = 10; int& b = a; int& c = b;). And as I assumed at first forwarding references are indeed (sort of) universal, and they will get converted back to the original one once compiled.

    template <typename R, typename... Args>
    class AnyWorker
    {
    public:
        AnyWorker(std::function<R(Args...)>&& in_pFunction, Args&&... inArgs)
            :pFunction{ in_pFunction }, savedArgs{ std::forward<Args>(inArgs)... }
        {
        }
    
        R Launch(Args&&... inArgs)
        {
            savedArgs = std::make_tuple(std::forward<Args>(inArgs)...);
            return Work();
        }
    
        R Launch()
        {
            return Work();
        }
    
    private:
        R Work()
        {
            return std::apply(pFunction, savedArgs);
        }
    
    private:
        std::function<R(Args...)> pFunction;
        std::tuple<Args...> savedArgs;
    };
    

    reminds me of Resource acquisition is initialization (RAII) idiom, though not exactly the case but RAII and classes are just coupled.

    This will work with all the versions of modifyVec function that I've wrote in the question.