Vii
Vii

Reputation: 841

C++ Passing struct function as parameter for a function in an inherited struct

I have two structs, struct B inherits from struct A. I want to pass a function as a parameter from struct B to struct A for struct A to use.

This is an example of what I want to achieve and the issue I'm running into, the full code would be TL;DR.

struct A
{
    int32 mynum;

    void Tick(float delta, bool doThis, void (funcparam)(float, bool, int32))
    {
        funcparam(delta, doThis, mynum);
    }
};

struct B : public A
{
    void RunThis(float deltaTime, bool doIt, int32 which)
    {
        // do something when called by Struct A
    };

    void DoSomething()
    {
        Tick(0.1f, true, &B::RunThis);
    };
};

The issue is with this line: Tick(0.1f, true, &B::RunThis); from the function void DoSomething() unless I've done it wrong from the beginning, but I imagine I am passing it wrong since it's still within the header of the struct that I'm currently defining?

Error (I have modified the error to fit my example, I don't think I messed up..):

error C2664: 'void A::Tick(float,bool,void (__cdecl *)(float,bool))': cannot convert argument 3 from 'void (__cdecl B::* )(float,bool)' to 'void (__cdecl *)(float,bool)'

Omitting the B:: from &B::RunThis of course doesn't solve anything.

Upvotes: 0

Views: 2081

Answers (2)

6502
6502

Reputation: 114481

Unfortunately C++ function pointers do not have context... in other words a pointer to function accepting an integer

void (*f)(int x)

is not allowed to have any data "bound" to the pointer and can only access parameters and global variables. A work-around implemented for some compilers are "trampoline" libraries that allow binding custom data to function pointers, but they all rely on dynamic code creation at runtime, something not possible within the boundaries of standard C++.

The solution in standard C++ is to use

std::function<void(int)>

instead of

void (*)(int)

An std::function object can keep data associated with the function and allows the creation of closures. It is also backward compatible with function pointers (i.e. a function pointer can be implicitly converted to an std::function object).

Using that your code can become for example

struct A {
    int32 mynum;
    void Tick(float delta, bool doThis,
              std::function<void(float, bool, int32)> funcparam) {
       funcparam(delta, doThis, mynum);
    }
};

struct B : public A {
    void RunThis(float deltaTime, bool doIt, int32 which) {
        // do something when called by Struct A
    };
    void DoSomething() {
        Tick(0.1f, true, [this](float dt, bool di, int32 w) {
            this->RunThis(dt, di, w);
        });
    }
};

where a lambda has been used to call the method.

A portable workaround if you cannot change the code accepting a function pointer is to have a preallocated set of function pointers for a given signature... for example

void *closure_data[10];
void (*closure_code[10])(void *, int);

void closure0(int x){ closure_code[0](closure_data[0], x); }
void closure1(int x){ closure_code[1](closure_data[1], x); }
void closure2(int x){ closure_code[2](closure_data[2], x); }
...
void closure9(int x){ closure_code[9](closure_data[9], x); }

void (*closures[10])(int) = {closure0,
                             closure1,
                             ...
                             closure9};

and then you need to "allocate" a closure i, putting your context data in the corresponding closure_data[i], the code in closure_code[i] and pass as function pointer closures[i].

Normally decent C++03 code or C code accepting function pointers for callbacks always provide a context parameter... i.e. the interface has the form

void Tick(void *context,
          void (*callback)(float, bool, int32, void *))

and will call the callback also passing the opaque void *context specified by the caller that provided the function pointer.

Upvotes: 0

BitTickler
BitTickler

Reputation: 11885

First option: Use a virtual function instead.

Works best if you have 1 and only 1 function per derived class anyway.

struct A {
    virtual void RunThis(float deltaTime, bool doIt, int32 which) = 0;
    void Tick(float delta, bool doThis )
    {
        //...
        RunThis(delta, doThis, which);
        // ...
    }
    virtual ~A() // virtual destructor...
    {}
};

struct B : public A
{
    virtual void RunThis(float deltaTime, bool doIt, int32 which )
    {
    }
    void DoSomething(/*...*/) 
    {
        // ...
        Tick(/*...*/);
    }

};

Second option: std::function + lambda or member function in struct B

#include <functional>
struct A 
{
     void Tick(float delta, bool doit, std::function<void(float,bool,int32)> action )
     {
     }
};

struct B : public struct A
{
    void DoSomething( /*...*/ )
    {
         // you can use a lambda, if convenient...
         Tick(/*...*/,
             [this](float delta, bool doit, int32_t which) -> void {
                // ...
             });
   }

   // or you can also use a pointer to member:
   void RunThis(/*...*/) { /* ... */ }
   void DoSomething(/*...*/)
   {
        std::function<void(float,bool,int32)> f = std::bind( &B::RunThis, this, _1,_2,_3);
        Tick(/*...*/, f);
   }
 };

Upvotes: 1

Related Questions