I ran into a problem while passing a function as a parameter in a method. My problem is that I want to be able to pass any void function with any type and amount of arguments and then store it in a data member. What I currently do is an overload for a void function without arguments and an overload for a void function with a vector argument of type std::any and then add an extra parameter for the vector. Here is an example of what I did:

typedef void (*FunctionT1)();
typedef void (*FunctionT2)(std::vector<std::any>);

class ExampleClass {
public:
    void saveFunction(FunctionT1 f) {
        emptyFunction = f;
    }
    void saveFunction(FunctionT2 f, std::vector<std::any> args) {
        vectorFunction = f;
        vectorFunctionArgs = args;
    }
private:
    FunctionT1 emptyFunction;

    FunctionT2 vecotorFunction;
    std::vector<std::any> vectorFunctionArgs;
}

Now this works and I'm able to do what I want but it's clearly a pretty bad solution. Is there a way to do this in an other way? (I was thinking of template packs but couldn't figure out how those would work)

3

There are 3 best solutions below

0
Jarod42 On BEST ANSWER

From what I understand, you want to call a function void(), where caller might register other function type with bind parameters. You might do it with std::function:

class ExampleClass {
public:
    void saveFunction(std::function<void()> f) {
        this->f = f;
    }
    void call() { f(); }
private:
    std::function<void()> f;
};

with usage

void foo();
void bar(std::vector<std::any>);

std::vector<std::any> v;

ExampleClass ex;

ex.SaveFunction(&foo); ex.call(); // foo();
ex.SaveFunction([&]() { bar(v); }); ex.call(); // bar(v);
// You might capture by copy/move if you prefer (instead of by reference)
0
A M On

I would like to show to you an example with an abstract factory.

With the factory pattern we have normally the problem, that all contstructors should have the same signature. But often we want to use functions with different parameter sets.

If you look in the below example, I can add functions with any number of parameters to the factory.

I know that this is not exactly what you want, but it may give you a hint on how to implement your own functionality.

#include <iostream>
#include <map>
#include <utility>
#include <any>


// Some demo classes ----------------------------------------------------------------------------------
struct Base {
    Base(int d) : data(d) {};
    virtual ~Base() { std::cout << "Destructor Base\n"; }
    virtual void print() { std::cout << "Print Base\n"; }
    int data{};
};
struct Child1 : public Base {
    Child1(int d, std::string s) : Base(d) { std::cout << "Constructor Child1 " << d << " " << s << "\n"; }
    virtual ~Child1() { std::cout << "Destructor Child1\n"; }
    virtual void print() { std::cout << "Print Child1: " << data << "\n"; }
};
struct Child2 : public Base {
    Child2(int d, char c, long l) : Base(d) { std::cout << "Constructor Child2 " << d << " " << c << " " << l << "\n"; }
    virtual ~Child2() { std::cout << "Destructor Child2\n"; }
    virtual void print() { std::cout << "Print Child2: " << data << "\n"; }
};
struct Child3 : public Base {
    Child3(int d, long l, char c, std::string s) : Base(d) { std::cout << "Constructor Child3 " << d << " " << l << " " << c << " " << s << "\n"; }
    virtual ~Child3() { std::cout << "Destructor Child3\n"; }
    virtual void print() { std::cout << "Print Child3: " << data << "\n"; }
};



using UPTRB = std::unique_ptr<Base>;


template <class Child, typename ...Args>
UPTRB createClass(Args...args) { return std::make_unique<Child>(args...); }

// The Factory ----------------------------------------------------------------------------------------
template <class Key, class Object>
class Factory
{
    std::map<Key, std::any> selector;
public:
    Factory() : selector() {}
    Factory(std::initializer_list<std::pair<const Key, std::any>> il) : selector(il) {}

    template<typename Function>
    void add(Key key, Function&& someFunction) { selector[key] = std::any(someFunction); };

    template <typename ... Args>
    Object create(Key key, Args ... args) {
        if (selector.find(key) != selector.end()) {
            return std::any_cast<std::add_pointer_t<Object(Args ...)>>(selector[key])(args...);
        }
        else return nullptr;
    }
};

int main()
{
    Factory<int, UPTRB> factory{
        {1, createClass<Child1, int, std::string>},
        {2, createClass<Child2, int, char, long>}
    };
    factory.add(3, createClass<Child3, int, long, char, std::string>);


    // Some test values
    std::string s1(" Hello1 "); std::string s3(" Hello3 ");
    int i = 1;  const int ci = 1;   int& ri = i;    const int& cri = i;   int&& rri = 1;

    UPTRB b1 = factory.create(1, 1, s1);
    UPTRB b2 = factory.create(2, 2, '2', 2L);
    UPTRB b3 = factory.create(3, 3, 3L, '3', s3);

    b1->print();
    b2->print();
    b3->print();
    b1 = factory.create(2, 4, '4', 4L);
    b1->print();
    return 0;
}
0
D-RAJ On

From reading the question and going through the comments, this is what I think best suits your problem,

#include <vector>

class ExampleClass {
public:
    ExampleClass() = default;

    /**
     * Save a function pointer. 
     * 
     * @param Function: The function poitner.
     * @return The index of the function.
     */
    template<class Return = void, class... Args>
    size_t SaveFunction(Return(*Function)(Args... args))
    {
        mFunctions.insert(mFunctions.end(), Function);
        return mFunctions.size() - 1;
    }

    /**
     * Call a saved function.
     * 
     * @tparam Return: The return type of the function.
     * @param index: The index of the function.
     * @param args: The arguments the function requires.
     * @return The return of the function.
     */
    template<class Return = void, class... Args>
    Return CallFunction(size_t index, const Args&... args)
    {
        typedef Return(*Function)(Args...);

        return reinterpret_cast<Function>(mFunctions[index])(args...);
    }

private:
    std::vector<void*> mFunctions;
};

And then you can save and call any function as so,

void Function() { std::cout << "Hello World\n"; }
void Add(int x, int y) { std::cout << x + y << std::endl; }

int main()
{
    ExampleClass c;
    size_t f1 = c.SaveFunction(Function);
    size_t f2 = c.SaveFunction(Add);

    c.CallFunction(f1);
    c.CallFunction(f2, 1, 2);
}

This works with any function with any amount of arguments.

Note: If the return type is always gonna be a void, you can replace the Return with void.