0

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.

Shahrooz
  • 196
  • 1
  • 14
  • I see no reason for savedArgs to exist given you use them immediately. – Yakk - Adam Nevraumont May 25 '23 at 21:54
  • no in this simplified version of the class I use them immediately – Shahrooz May 25 '23 at 21:55
  • 5
    Do note that storing references can lead to undefined behavior. If the objects are temporary they **do not** get their lifetime extended like they would if binding to function local references. You have to store a tuple of actual objects, and then you can use those objects with the function call. – NathanOliver May 25 '23 at 21:56
  • 4
    There's no such thing as a "forward reference". Only type parameters that can be deduced as either lvalue or rvalue references, and allow perfect forwarding. You'd still be storing either lvalue or rvalue references with no lifetime extension. – Useless May 25 '23 at 21:58
  • so you mean there's no such thing as universal references in c++ to capture any kind of parameter type? @Useless – Shahrooz May 25 '23 at 22:01
  • 3
    Yep. Universal references are a useful abstraction, that ceases to exist when type deduction and so on happen. It's nice to write perfect-forwarding function templates, but there is no concrete type corresponding to a "forwarding" or "universal" reference. – Useless May 25 '23 at 22:09
  • 1
    Please don't spam C++ versions. I doubt you use both versions in a single project. – 273K May 25 '23 at 22:09
  • @Useless could you write a complete answer ? alongside thing you mentioned here – Shahrooz May 25 '23 at 22:14
  • On the one hand I would like to help and answer, on the other hand the shown code requires me to add missing #include just to get the error in the question. It kills my desire to help OP. The error is pretty clear: must explicitly initialize the member 'savedArgs' with references. – 273K May 25 '23 at 22:33
  • You know who ever answer questions get the rep, I have almost already corrected my wrong assumption and understood my mistake, but any well and properly written (without seeking things like you forget to include this or that, like I'm a bachelor's college student) and clarifying answer is both appreciated and accepted. @273K – Shahrooz May 25 '23 at 22:54
  • I mean, I have infinite rep. I answer questions for fun. My problem is your bug is clear: references have no zero arg ctor. To fix it in your code, you remove the tuple, as it is useless. There is some different design that you think needs this tuple. Can you describe it? – Yakk - Adam Nevraumont May 26 '23 at 01:14
  • 1
    You're not using forwarding references here at all. Forwarding references must use a type declared in a function template. What you've got here are non-template members that refer to parameters of the containing type, so the types here are all rvalue references. You may need to use a helper template for choosing/converting the from/to the tuple types, e.g. something producing these mappings from function parameter type to tuple type `T->T`, `T&&->T`, `T&->T&`. Furthermore you'll need something like `std::optional` to store references, since tuples containing references cannot be reassigned... – fabian May 26 '23 at 07:51
  • I chose tuple because I could save the args and make use of std::apply ( to call the apply later ) @Yakk-AdamNevraumont – Shahrooz May 26 '23 at 08:48
  • @Shahrooz Then do so in your [MCVE] please. And do make it an MCVE! – Yakk - Adam Nevraumont May 26 '23 at 14:36

1 Answers1

2

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.

Shahrooz
  • 196
  • 1
  • 14