Home > Back-end >  How to store a specific object's member function of known signature in a variable in C ?
How to store a specific object's member function of known signature in a variable in C ?

Time:05-02

What is the recommended way to store a reference to a non-static member function of a specific signature on some object instance? In a way where the calling code needs not to know of the object's class (i.e. no casting), just that the function is the correct signature.

For example, if there are two different classes with functions of the same void(int) signature, like

class Foo {
public:
  int myInt;
  
  void multMyInt(int multiplier) {
    myInt *= multiplier;
  }
};

class Bar {
public:
  bool isBig;
  
  void setMySize(int size) {
    isBig = size > 100;
  }
};

What should this code look like?

int main() {
  Foo* myFoo = new Foo();
  Bar* myBar = new Bar();

  int x = 10;

  callIntFunc(x, /* myFoo->multMyInt() */);
  callIntFunc(x, /* myBar->setMySize() */);
}

// Calls any function which matches void(int).
void callIntFunc(int inInt, /* function stored in some way */) {
  // call given function with inInt as the parameter
}

What is the recommended approach to doing this using standard library only?

p.s. Apologies if this is a repeat question. I have searched in any terms that came to mind, and couldn't find it.

CodePudding user response:

As you have to store not only the pointer to the member function but also the object which the function should access, you need a way to store both in a single kind of object.

As C has lambda functions you simply can take them and use them like in the following example modified from your code.

class Foo {
    public:
        int myInt;

        void multMyInt(int multiplier) {
            std::cout << "Calls multMyInt" << std::endl;
            myInt *= multiplier;
        }
};

class Bar {
    public:
        bool isBig;

        void setMySize(int size) {
            std::cout << "Calls setMySize" << std::endl;
            isBig = size > 100; 
        }
};

// Calls any function which matches void(int).
void callIntFunc(int inInt, auto& func )
{
    func(inInt);
}


int main() {
    Foo* myFoo = new Foo();
    Bar* myBar = new Bar();

    auto multMyInt = [myFoo](int parm){ myFoo->multMyInt(parm);};
    auto setMySize = [myBar](int parm){ myBar->setMySize(parm);};

    int x = 10;



    callIntFunc(x, multMyInt);
    callIntFunc(x, setMySize);
}

In times before C 11 you have to use std::bind but that is really not longer needed.

Instead of using auto in connection with lambda, you can also go with std::function which results in:

// Calls any function which matches void(int).
void callIntFunc(int inInt, std::function<void(int)>& func )
{
    func(inInt);
}


int main() {
    Foo* myFoo = new Foo();
    Bar* myBar = new Bar();

    std::function<void(int)> multMyInt = [myFoo](int parm){ myFoo->multMyInt(parm);};
    std::function<void(int)> setMySize = [myBar](int parm){ myBar->setMySize(parm);};

    int x = 10;



    callIntFunc(x, multMyInt);
    callIntFunc(x, setMySize);
}

The templated version which uses auto needs some more program memory as it generates for each function type a new instance, but it is fast.

The std::function version needs less program space but results in a bit runtime overhead. It depends on your needs what you prefer!

Update from comments:

There is nothing special regarding the scope in case of using lambdas as in every other situation where you use pointers or references. But you have to take care:

  • if you capture by reference, the referenced object must be alive if you call the function / lambda.
  • if you capture a copy of the object, you get a new independent copy. As this, it is valid as long the lambda is alive, but you reference to a copy and not the original object.

What is the type of auto:

Every lambda results in a new type. The type itself is always hidden and generated by the implementation. That is why you have to use auto.

CodePudding user response:

Use std::bind_front. It does the same thing as a custom lambda suggested in the other answer, but the syntax is less verbose.

auto func = std::bind_front(&Foo::multMyInt, myFoo);
func(42);

Note that we pass the class pointer as the second argument. If we instead pass by value, the class instance will be copied into the functor.

Here auto expands to some unspecified type, but you can replace it with std::function<void(int)>.

  • Related