Passing function pointers to a class without knowing the function signature

Hi,

I am relatively new to C++ and I am working on a personal project for practicing where I need to create a class that can take function pointers with different signatures.

The idea is that each instance of the class will store a pointer to a specific function and can call that function whenever I want.

To give a better idea of what I want, let me explain with a little bit more detail what I am trying to do. The project I am working on is a very basic console game and the object I am trying to create is an object that would store details on each location the player can access in the game.

(DISCLAIMER: I know that most of what I describe later is probably an overkill for a basic console game. I can easily make the whole game in a couple of files using just simple functions and I know how to do that. But the idea here is that I wanted to practice more advanced C++ techniques without having to figure out a complex project. So, since I know how to make a basic console game, I thought it would be a good idea to try and figure out how to achieve the same result but with more advanced techniques)

One of the details that I think should be stored is what happens in each location, basically the text that is output to the screen describing what happens and prompting the user to take action.

Since this would be different for each location, I can't just declare and implement a function in the class.

One way of solving this issue is to create a base class with a virtual function and then implement this function in a series of derived classes, each defining a new location.

The problem I have with this approach is that it makes each location a class that can be inherited further and instanced, which I don't need as I will only have 1 instance of each location.

I can of course just create 1 instance of the class, but I wanted to see if there is a way to avoid having to create separate classes for each location.

This why I started thinking of function pointers.

Now, I know I can declare a function pointer and initialise it in a class like that:

1
2
3
4
5
6
7
8
9
class Test
{
    public:
        Test(void (*p)())
            : print{p}
        {}
    private:
        void (*print)();
};


That works fine as long as the function returns void and accepts no arguments.

So, I thought maybe I can do that with a template:

1
2
3
4
5
6
7
8
9
template <typename Function>
class Test
{
    public:
        Test(Function *p)
            : print{p}
        {}
        Function *print;
};


This actually works well. I can now have a class that accepts different functions with different return types.

I can create instances of the class in the following way:

1
2
3
void print();

Test<void ()> a {print};


However, I have one problem with this approach. Because it is a class template, I can't have a pointer that I want to use to point to instances of Test class regardless of the function that is passed to them.

For instance, if I declare the following pointer:

 
Test<void ()> *b = &a;


There is no way to re-assign that pointer to another instance of Test class unless the function pointer passed to it also returns void and accepts no arguments. Otherwise, I have to create a new pointer.

Is there a way to avoid that? Is there a better way of achieving what I am looking for than using function pointers?

Thank you very much and sorry for the long message.
Last edited on
Yes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class AbstractTest{
public:
    virtual ~AbstractTest(){}
    virtual void do_something() = 0;
};

template <typename Function>
class Test : public AbstractTest{
    Function *print;
public:
    Test(Function *p): print{p}{}
    void do_something() override{
        this->print();
    }
};

//Note: don't use new. I'm being lazy.
AbstractTest *p = new Test<void()>(foo);
p = new Test<std::string()>(bar);


EDIT: Also, another possibility is this:
1
2
3
4
5
6
7
8
9
10
11
12
class Test{
    std::function<void()> print;
public:
    template <typename Function>
    Test(Function *p): print([p](){ p(); }){}
    void do_something(){
        this->print();
    }
};

Test *p = new Test(foo);
p = new Test(bar);
Last edited on
Thank you very much, helios. I think this might work. I will test once I am back home and let you know how it works.
I tried the two solutions you provided, but whenever I try to use a non-void function, I get the following error:

 
void value not ignored as it ought to be

This is using g++ on Linux for compiling.
Last edited on
Post the code.
Here you go.

NOTE: I am using new and delete just for testing purposes at the moment.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <iostream>
#include <functional>

class AbstractTest
{
public:
    virtual ~AbstractTest(){}
    virtual void do_something() = 0;
};

template <typename Function>
class Test1 : public AbstractTest
{
    Function *print;
public:
    Test1(Function *p): print{p}{}
    void do_something() override{
        this->print();
    }
};


class Test2
{
    std::function<void()> print;
public:
    template <typename Function>
    Test2(Function *p): print([p](){ p(); }){}
    void do_something(){
        this->print();
    }
};



void func1()
{
    std::cout << "Hello" << std::endl;
}


int func2()
{
    int x = 10;

    return x;
}


int main()
{
    // Using first method
    AbstractTest *p1 = new Test1<void()>(func1);

    p1->do_something();

    p1 = new Test1<int()>(func2);

    int x = p1->do_something(); // compiler error: void value not ignored as it ought to be

    std::cout << x << std::endl;


    // Using second method
    Test2 *p2 = new Test2(func1);

    p2->do_something();

    p2 = new Test2(func2);

    int y = p2->do_something(); // compiler error: void value not ignored as it ought to be

    std::cout << y << std::endl;

    delete p1;
    delete p2;
}
Okay, now what you want to do is impossible.

The return type of AbstractTest::do_something() must be fixed and it must be specific. If it's void then you must discard any non-void value returned by the function.
1
2
3
p1 = new Test1<int()>(func2);
int x = p1->do_something(); //What are you assigning here? Test1::do_something()
                            //doesn't return anything. 

If the return type is non-void then you have to figure out what to do when the constructor is passed a void function.
1
2
3
4
5
6
7
8
9
10
class Test2
{
    std::function<int()> print;
public:
    template <typename Function>
    Test2(Function *p): print([p](){ /* What do I doo?? */ }){}
    int do_something(){
        return this->print();
    }
};

C++ is statically typed. There's no way to have a generic callable object that accepts an unknown number of parameters and returns a value of unknown type or nothing.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <magic>

void foo(){
    std::cout << "Hello, World!\n";
}

void bar(std::string s){
    std::cout << "Hello, World: " << s << "\n";
}

int plus(int a, int b){
    return a + b;
}

//This is completely impossible.

int main(){
    std::magic m = foo;
    m();
    m = bar;
    m("omae wa mou shindeiru");
    m = plus;
    std::cout << m(41, 1) << std::endl;
}
Last edited on
a little over my head... haven't ever tried to do this, but
..
you could (??) return some kind of struct that had a pointer and a type. If type were void, pointer would be null, otherwise, its some enum or whatever representing the types and the pointer is one of 'that type'.

AbdelrahmanSaid, this is a big deep rabbit hole. You can hack around at it and force fit it to work, but any time you want to go TOO generic, to the point that anything is everything and everything can do anything, it becomes a giant confusing mess and its going to end up being some sort of C like hack where everything is a wad of bytes lumped up by pointers that you then have to unravel back into whatever it really needs to be. At this point, its much better to just have specific things, or 'less generic' things, that are usable without the voodoo. Worse, you are now returning a bunch of junk when nothing needed to be returned. Across the board its going to be inefficient AND confusing: there are no benefits.
Last edited on
Yeah.. That is what I thought would be the case, but I just wanted to make sure I am not missing anything. Thank you very much.
a little over my head... haven't ever tried to do this, but
..
you could (??) return some kind of struct that had a pointer and a type. If type were void, pointer would be null, otherwise, its some enum or whatever representing the types and the pointer is one of 'that type'.

AbdelrahmanSaid, this is a big deep rabbit hole. You can hack around at it and force fit it to work, but any time you want to go TOO generic, to the point that anything is everything and everything can do anything, it becomes a giant confusing mess and its going to end up being some sort of C like hack where everything is a wad of bytes lumped up by pointers that you then have to unravel back into whatever it really needs to be. At this point, its much better to just have specific things, or 'less generic' things, that are usable without the voodoo. Worse, you are now returning a bunch of junk when nothing needed to be returned. Across the board its going to be inefficient AND confusing: there are no benefits.


Thank you, jonnin. I don't want to go too generic. At the end of the day, I am not looking to design some interface or a library that could be used everywhere and for every kind of situation. And, I am not too much into the vodoo anyway :D. I was just curious if what I am looking for is something that is possible to do or should I just specific as I expected I would need to.

Thanks for you help.
Topic archived. No new replies allowed.