Search code examples
c++pointer-to-member

Understanding error with taking address of non-static member to form pointer to member function?


I have an issue that I'm not sure how to get around.

I am using a library whose instances of the class are constructed by being passed references to a few functions.

void func_on_enter() {...}
void func() {...}
void func_on_exit(){...}

State state_a(&func_on_enter, &func, &func_on_exit);

I am writing a class that contains an instance of this class, and as such have attempted something like this:

class MyClass{
private:
  void func_on_enter() {...}
  void func() {...}
  void func_on_exit(){...}

  State _state_a;

public:
  MyClass() : _state_a(&func_on_enter, &func, &func_on_exit) {}
};

I get the error "ISO C++ forbids taking the address of an unqualified or parenthesized non-static member function to form a pointer to member function. Say '&MyClass::_func_on_enter'" (and the same for the other two methods obviously). The compiler helpfully provides a solution, which I tried.

However the compiler then reminds me that there is no matching function for a call to 'State::State(void (MyClass::*)(), void (MyClass::*)(), (MyClass::*)())' and that it has no known conversion from 'void (MyClass::*)()' to 'void(*)()'.

I tried reading up on similar questions about this error but can't seem to find a solution that I both understand and can help in this situation.

I considered trying to add an overload constructor to State, but this seems absurd to do on a class by class case, considering that I could be calling it from any other class.

I also thought for a while that I could just make the functions static (as they're the same for all instances of MyClass), but I quickly remembered that they would then be unable to use non-static member variables of MyClass.

Is there perhaps a way that I can provide a conversion or provide the pointers for the State constructor in another way?

I could really do with some help on this, as I'm quite stuck for ideas on how to move forward. Any help or hints would be greatly appreciated!


Solution

  • Pointers to non-static member functions cannot be converted to plain function pointers. A void (MyClass::*)() requires a MyClass object to operate on. Raw pointers can't hold any sort of state though, so there's no way for a void(*)() to store the information about which MyClass object to operate on.

    Luckily, the standard library has a class template designed for just this purpose: std::function. std::function is a more-or-less drop-in replacement for raw function pointers, but it can hold any type of callable object that's compatible with a given signature. That means it can hold the state required to call back to a specific instance of a class:

    class State {
    private:
      std::function<void()> on_enter;
      std::function<void()> func;
      std::function<void()> on_exit;
    
    public:
      State(std::function<void()> on_enter, std::function<void()> func, std::function<void()> on_exit)
        : on_enter{std::move(on_enter)},
          func{std::move(func)},
          on_exit{std::move(on_exit)}
      {}
    
      void do_something() {
        on_enter();
        // stuff
        func();
        // more stuff
        on_exit();
      }
    };
    
    class MyClass {
    private:
      void func_on_enter() {}
      void func() {}
      void func_on_exit(){}
    
      State state_a;
    
    public:
      MyClass()
        : state_a([this]() { func_on_enter(); },
                  [this]() { func(); },
                  [this]() { func_on_exit(); })
      {}
    };
    

    Live Demo

    Here, instead of passing pointers to your member functions directly, I've wrapped them in lambdas that capture the this pointer of the object to be called back. Now the State object is able to call those members, and they know which instance of MyClass to operate on.

    Be careful about object lifetime though. Since state_a holds pointers back to its MyClass instance, you'll need to ensure MyClass's copy/move constructor and assignment operators do the right thing. The default implementations provided by the compiler aren't sufficient.