Xorr
Xorr

Reputation: 35

C++ Take a type as a parameter of an unknown class function?

I am trying to make a class function take another class unknown function. I hope that made any sense. What I am trying to do is... if button1 (or any declared button) is clicked, it will execute whatever function (any "void function") that was inputted into the first parameter of function Event__MouseClicked.

bool MouseClicked( void ){ /**/ };

namespace XE
{
    class Window
    {
    public:
        const char* Title;
    };

    class Button
    {
    public:
        void Event__MouseClicked( void( *Function )( void ) ) {
            if( MouseClicked( ) )
                ( *Function )( );
        }
    };
};

class XorrWindow1 : public XE::Window
{
public:
    XorrWindow1( void ) {
        Initialize( );
    }
protected:
    ~XorrWindow1( void );
private:
    XE::Button Button1;
    XE::Button Button2;
private:
    // EVENT PROTOTYPES
    void DoSomething( void );
    void RandomFunction( void );

    void Initialize( void )
    {
        // INITIALIZE CONTROLS
        // AND OTHER STUFF BELOW
        this->Title = "XorrWindow1";

        // How can I resolve this problem?
        this->Button1.Event__MouseClicked( &XorrWindow1::DoSomething );
        this->Button2.Event__MouseClicked( &XorrWindow1::RandomFunction );
    };
};

void XorrWindow1::DoSomething( void ) {
    ::MessageBoxA( NULL, this->Title, "Button1 clicked!", MB_OK );
};

void XorrWindow1::RandomFunction( void ) {
    ::MessageBoxA( NULL, this->Title, "Button2 clicked!", MB_OK );
};

The error is this:

'XE::Button::Event__MouseClicked' : cannot convert parameter 1 from 'void (__thiscall XorrWindow1::* )(void)' to 'void (__cdecl *)(void)'

I absolutely understand what caused the error. But I don't know how to fix it because it must be able to take any unknown function of class Window1.

Upvotes: 2

Views: 1467

Answers (3)

Marcelo Cantos
Marcelo Cantos

Reputation: 186118

You can't pass a member function pointer as a free function pointer, since there is no way to capture this and pass it in as required.

There are many and varied solutions to this problem. The most elegant, if you have C++11, is to pass a std::function<void()>:

virtual void Event__MouseClicked(const std::function<void()>& f) {
    if (MouseClicked()) f();
}
⋮
Button1.Event__MouseClicked([&]{ DoSomething(); });

EDIT: I almost forgot to mention that with lambdas, you can even do away with the callback member functions:

Button1.Event__MouseClicked([&]{
    ::MessageBoxA( NULL, this->Title, "Button1 clicked!", MB_OK );
});

Because this isn't a template function, you can override it in derived classes. Note, however, that GUI frameworks don't generally require that you derive from classes like Button in order to customise their behaviour, and they also don't pass the callback in as part of the event. The common pattern is for the button to hold onto the callback function and invoke it as appropriate. E.g.:

class Button {
⋮
public:
    void onClicked(std::function<void(Button*)>& f) { clicked_ = f; }
    void click() { clicked_(this); }
private:
    std::function<void(Button*)> clicked_;

    // Internal click handler responds to OS click events.
    void handleClickEvent(…) { clicked_(this); }
};

The above isn't the world's best design, but it should give you a sense of what I'm talking about.

Upvotes: 2

David
David

Reputation: 28178

Like Karel Petranek said, you need templates to accommodate all cases. Here is the most generic method (utilized by STL everywhere):

template<typename f>
void Event__MouseClicked(f&& func)
{
    if( MouseClicked( ) )
        func();
}

Basically Event__MouseClicked accepts a callable object. In your case you would call it like Marcelo Cantos described:

Button1.Event__MouseClicked([&]{ DoSomething(); });

But the benefit of this method is that passing any callable object that takes no arguments into Event__MouseClicked will compile and work. You can pass a function pointer, the result of std::bind, a std::function, a lambda, etc etc.

This is more optimal than forcibly accepting a std::function in some cases, for example when accepting a straight function pointer.

Upvotes: 3

Karel Petranek
Karel Petranek

Reputation: 15164

You will need to use templates to accommodate for all possible classes:

        template <typename T>
        void Event__MouseClicked(T *object, void( T::*Function )( void ) ) {
            if( MouseClicked( ) )
                ( object->*Function )( );
        }

Use:

this->Button2.Event__MouseClicked( this, &XorrWindow1::RandomFunction );

Note though that you will lose the ability to override this method in subclasses as templated functions cannot be virtual. You also need to pass the "this" pointer along with the method pointer.

Upvotes: 1

Related Questions