3

EDIT: std::function + lambda to the rescue. See checked answer.

EDIT: I added more detail, I want a generic solution, not one bound to Class B's definition.

In the example below, I try to set a function pointer in one class to a function (of the same function pointer type) from an instance of another class, and Microsoft C++ is telling me:

C++ a pointer to a bound function may only be used to call the function

Is it because the object containing the callback might be destructed before the first object? I'm translating some Python to C++ and this pattern doesn't seem to be a problem in Python.

I find the workaround is to make the function static (and the member data), but I'm just hacking until the compiler shuts up and the code works, not my favourite mode of programming.

If this pattern isn't allowed, any suggestions on how to do this dynamically without static objects/functions?

#include <iostream>
#include <string>

typedef void (*callback_function_a)(int);

struct A 
{
    A() {
        cbk = nullptr;
    };
    void print_int(void) 
    {
        if (cbk != nullptr) {
            cbk(10);
        }
    }
    void set_callback(callback_function_a _cbk)
    {
        cbk = _cbk;
    };
    callback_function_a cbk;
};

struct B 
{
    B(std::string _header) 
    {
        header = _header;
    }
    void print(int x) {
        std::cout << header << x << std::endl;
    }
    std::string header;
};

void print(int x)
{
    std::cout << "Non-member func: " << x << std::endl;
}

int main() 
{
    A classA;
    B classB(std::string("MessageFrom classB:"));
    /// How do I make both of these work? I want to be able to pass in any 
    /// function of type callback_function_a?
    classA.set_callback(print); // OK
    classA.set_callback(classB.print); // <--- error here
}
JeJo
  • 30,635
  • 6
  • 49
  • 88
PeterT
  • 920
  • 8
  • 20
  • Does this answer your question? [Function pointer to member function](https://stackoverflow.com/questions/2402579/function-pointer-to-member-function) – Adrian Mole Jun 14 '20 at 16:45
  • 1
    `std::function` might help. – Jarod42 Jun 14 '20 at 16:46
  • @AdrianMole Unfortunately no because now the type of callback function is only member function from B::. What if I wanted to pass in a function not from an object? I updated my question. I see that there is a difference between the function pointer typedefs, looking for a generic solution. – PeterT Jun 14 '20 at 17:07

3 Answers3

3

You have a couple of issues with your code.

Firstly the member function pointer and the free function pointer types are different. For the member function pointer, you should have

typedef void (B::*callback_function_a)(int);
//            ^^^^
// or
// using callback_function_a = void (B::*)(int);

Secondly, passing the member functions should have the syntax &ClassName::MemberFunction, meaning, you had to

classA.set_callback(&B::print); 

Last but not the least, you need to have an instance of the class to invoke its member function. Therefore here,

void print_int(void) {
   if (cbk != nullptr) {
      // cbk(10);  // will not work
   }
}

simply calling the member function pointer will not work. You should have an instance of the B class to invoke its member. You need, something like

#include <functional>  // std::invoke (required C++17)

void print_int()
{
   if (cbk != nullptr)
   {
      (/*instance of B class*/.*cbk)(10);
      // or simply using `std::invoke`
      // std::invoke(cbk, /*instance of B*/, 10);
   }
}
JeJo
  • 30,635
  • 6
  • 49
  • 88
  • Thanks, but now I can only pass in callbacks that are of type B::*callback_function. Which is not what I want, I want to be able to pass in any callback function, even ones not from a class B. Question updated. – PeterT Jun 14 '20 at 17:06
3

A function pointer to a free function and a function pointer to a member-function are two different things. To work around that we need some kind of wrapper.

This is one of the good use cases for std::function. It's a type-erased callable with a given signature.

To make it more generic you should make set_callback a template so you can accept any kind of callable object that is assignable to a std::function.

#include <iostream>
#include <string>
#include <functional>

struct A {
    A() {
        cbk = nullptr;
    };
    void print_int(void) {
        if (cbk != nullptr) {
            cbk(10);
        }
    }
    template <typename Func>
    void set_callback(Func _cbk) {
        cbk = _cbk;
    };
    std::function<void(int)> cbk;
};

struct B {
    B(std::string _header) {
        header = _header;
    }
    void print(int x) {
        std::cout << header << x << std::endl;
    }
    std::string header;
};

void print(int x) {
    std::cout << "Non-member func: " << x << std::endl;
}

int
main() {
    A classA;
    B classB(std::string("MessageFrom classB:"));
/// How do I make both of these work? I want to be able to pass in any 
/// function of type callback_function_a?
    classA.set_callback(print); // OK
    classA.set_callback([&](int i) { classB.print(i); }); // <--- now works as well
}

If you're not familiar with lambdas like [&](int i) { classB.print(i); } have a look here.

super
  • 12,335
  • 2
  • 19
  • 29
  • Ah, I missed the lambda. That's what I needed. Whew I'm in over my head here. I'm used to lambdas in JavaScript and Python, but the syntax is nutty in C++ (not to mention there are different implemntations of lambdas). – PeterT Jun 14 '20 at 17:53
1

In addition to Jejo's excellent answer there's one thing that can be added and that is, You can make the member function static and then pass it directly as an argument set_callback().

Example:

class B {
...
static void print(int x) {
    // can't use class members in static member functions
    std::cout << /* header << */ x << std::endl;
}
...
};

In main()

classA.set_callback(&B::print); 
Waqar
  • 8,558
  • 4
  • 35
  • 43
  • Yes, that was my original solution in the question. It also requires making member data referenced in the static code static as well, which is a larger problem. – PeterT Jun 14 '20 at 17:10
  • You *could* use a static member function and pass a pointer to a class object to it as an argument. – Adrian Mole Jun 14 '20 at 18:02