Search code examples
c++function-pointerstypedefpointer-to-member

Call function implementing type on instance by a pointer


I'm trying to implement subscriber-publisher pattern. My base class Subscriber doesn't have a listener method, it declares type Handler instead. The idea behind this is that a derived class will be able to have multiple handlers which will implement this type and can be passed to a Publisher. I'm trying to invoke a handler in Notify method, but I'm getting an error which states expression must have pointer-to-member type. How can I cast my function pointer to a member type and is it even possible with my approach?

class Subscriber {
public:
    typedef void (*Handler)();
};

struct Subscription {
    Subscriber *instance;
    Subscriber::Handler handler;
};

class Publisher {
protected:
    std::vector<Subscription> subscriptions;

public:
    virtual void AddSubscriber(Subscription subscription) {
        this->subscriptions.push_back(subscription);
    };

    virtual void Notify() {
        for (auto subscription : this->subscriptions) {
            auto i = subscription.instance;
            auto h = subscription.handler;

            (i->*h)();
        }
    };
};

My goal is to make an expandable system to be able to do something like this

struct KeyboardSubscription : Subscription {
    Event event_type;
    Key listened_key;
}

class GUI : Subscriber {
    void OpenMenu() { ... }; // implementing Subscriber::Handler
    void CloseMenu() { ... }; // implementing Subscriber::Handler too
}

class Keyboard : Publisher {
    void Notify() {
        for (KeyboardSubscription subscription : this->subscriptions) {
            auto i = subscription.instance;
            auto h = subscription.handler;
            
            if (subscription.event_type == this->event_type &&
                subscription.listened_key == this->pressed_key)
                (i->*h)();
        }
    }
}

int main() {
   // init GUI and Keyboard...

   KeyboardSubscription sub1;
   sub1.instance = gui_instance;
   sub1.handler = &GUI::OpenMenu;
   sub1.event_type = Key_Down;
   sub1.listened_key = Enter;

   KeyboardSubscription sub2;
   sub2.instance = gui_instance;
   sub2.handler = &GUI::CloseMenu;
   sub2.event_type = Key_Down;
   sub2.listened_key = Esc;

   keyboard_instance.AddSubscription((Subscription)sub1);
   keyboard_instance.AddSubscription((Subscription)sub2);
}

Solution

  • I've achieved what I wanted. I just needed to declare a function type like this:

    class Subscriber {
    public:
        typedef void (Subscriber::*Handler)();
    };
    

    Here's a full example which compiles without any warnings and works as expected

    #include <iostream>
    #include <vector>
    
    class Subscriber {
    public:
        typedef void (Subscriber::*Handler)();
    };
    
    struct Subscription {
        Subscriber *instance;
        Subscriber::Handler handler;
    };
    
    class Publisher {
    protected:
        std::vector<Subscription*> subscriptions;
    
    public:
        void AddSubscription(Subscription *subscription) {
            this->subscriptions.push_back(subscription);
        }
    
        virtual void Notify() {
            for (auto subscription : this->subscriptions) {
                auto i = subscription->instance;
                auto h = subscription->handler;
    
                (i->*h)();
            }
        }
    };
    
    struct KeyboardSubscription : Subscription {
        int event_type;
        int listened_key;
    };
    
    class GUI : Subscriber {
    public:
        void OpenMenu() { // implementing Subscriber::Handler
            std::cout << "Open menu" << std::endl;
        }
        void CloseMenu() { // implementing Subscriber::Handler too
            std::cout << "Close menu" << std::endl;
        }
    };
    
    class Keyboard : Publisher {
    private:
        int event_type;
        int pressed_key;
    
    public:
        void AddSubscription(Subscription *subscription) {
            Publisher::AddSubscription(subscription);
        }
    
        void Notify() {
            for (auto sub : this->subscriptions) {
                KeyboardSubscription* subscription = static_cast<KeyboardSubscription*>(sub);
                
                auto i = subscription->instance;
                auto h = subscription->handler;
                
                if (subscription->event_type == this->event_type &&
                    subscription->listened_key == this->pressed_key)
                    (i->*h)();
            }
        }
    
        void Event1() {
            this->event_type = 10;
            this->pressed_key = 1;
    
            this->Notify();
        }
    
        void Event2() {
            this->event_type = 20;
            this->pressed_key = 2;
    
            this->Notify();
        }
    };
    
    int main() {
        GUI *gui_instance = new GUI;
        Keyboard *keyboard_instance = new Keyboard;
    
        KeyboardSubscription *sub1 = new KeyboardSubscription;
        sub1->instance = (Subscriber*)gui_instance;
        sub1->handler = (Subscriber::Handler)(&GUI::OpenMenu);
        sub1->event_type = 10;
        sub1->listened_key = 1;
    
        KeyboardSubscription *sub2 = new KeyboardSubscription;
        sub2->instance = (Subscriber*)gui_instance;
        sub2->handler = (Subscriber::Handler)(&GUI::CloseMenu);
        sub2->event_type = 20;
        sub2->listened_key = 2;
    
        keyboard_instance->AddSubscription((Subscription*)sub1);
        keyboard_instance->AddSubscription((Subscription*)sub2);
    
        keyboard_instance->Event1();
        keyboard_instance->Event2();
    }
    

    And the output is as follows:

    Open menu
    Close menu