Search code examples
c++templateseventsdynamic-castdowncast

Reuse code that should be run on base object type with templates in overloaded virtual function for derived object types


Context:

I got the next hierarchy of structers:

Event -> MouseEvent    -> MouseButtonEvent
                       -> MouseWheelEvent
                       -> ...

      -> KeyboardEvent -> KeyEvent
                       -> InputEvent
                       -> ...
      -> ...

Potentially there could be a lot of different event types. There is also a components heirarchy. The base class is Component:

Component.h: how i want it to be


class Component
{
    ...
    /* These could be overriden in derived to handle this events. */
    virtual void handleEvent(const MouseButtonEvent&);
    virtual void handleEvent(const MouseWheelEvent&);
    virtual void handleEvent(const KeyEvent&);
    virtual void handleEvent(const InputEvent&);
    ...

    ...
    template<typename GMouseEvent>
    void handleMouseEvent(const GMouseEvent& e)
    {
        if (!children.empty())
        {
            // Pay attention: I need to cast to real Event type to be able to use event properties here.
            const auto& eMouse = static_cast<const MouseEvent&>(e);

            auto eCopy = eMouse;

            for (auto child : children)
            {
                 // point is a property of MouseEvent
                 eCopy.point = child->transformPointToLocal(eMouse.point);
                 if (child->predicateOnMouseEventPropery(eMouse.point))
                      return child->handleEvent(static_cast<GMouseEvent&>(eCopy)); // Need to save GMouseEvent type to call proper virtual handler
            }
        }
    }

    template<typename GNonMouseEvent>
    void handleNonMouseEvent(const GNonMouseEvent& e)
    {
        for (auto child : children)
            child->handleEvent(e);   // Also need to preserve original call type to call virtual method
    }

    ...

    std::vector<Component*> children;
}


...


void Component::handleEvent(const MouseButtonEvent& e)
{
    handleMouseEvent<MouseButtonEvent>(e);
}

void Component::handleEvent(const MouseWheelEvent& e)
{
    handleMouseEvent<MouseWheelEvent>(e);
}

void Component::handleEvent(const KeyEvent& e)
{
    handleNonMouseEvent<KeyEvent>(e);
}

void Component::handleEvent(const InputEvent& e)
{
    handleNonMouseEvent<InputEvent>(e);
}

Component.h: how it is actually


class Component
{
    ...
    /* These could be overriden in derived to handle this events. */
    virtual void handleEvent(const MouseButtonEvent&);
    virtual void handleEvent(const MouseWheelEvent&);
    virtual void handleEvent(const KeyEvent&);
    virtual void handleEvent(const InputEvent&);
    ...

    std::vector<Component*> children;
}


...


void Component::handleEvent(const MouseButtonEvent& e)
{
    if (!children.empty())
    {
         auto eCopy = e;

         for (auto child : children)
         {
             // point is a property of MouseEvent that is the base of MouseButtonEvent
             eCopy.point = child->transformPointToLocal(e.point);
             if (child->predicateOnMouseEventPropery(e.point))
                 return child->handleEvent(eCopy);
        }
    }
}

void Component::handleEvent(const MouseWheelEvent& e)
{
    if (!children.empty())
    {
         auto eCopy = e;

         for (auto child : children)
         {
             // point is a property of MouseEvent that is the base of MouseWheelEvent
             eCopy.point = child->transformPointToLocal(e.point);
             if (child->predicateOnMouseEventPropery(e.point))
                 return child->handleEvent(eCopy);
        }
    }
}

void Component::handleEvent(const KeyEvent& e)
{
    for (auto child : children)
        child->handleEvent(e);
}

void Component::handleEvent(const InputEvent& e)
{
    for (auto child : children)
        child->handleEvent(e);
}

MyComponent.h:


class MyComponent : public Component
{
    virtual void handleEvent(const MouseButtonEvent& e);
    virtual void handleEvent(const KeyEvent& e);
    ...
}


void MyComponent::handleEvent(const MouseButtonEvent& e)
{
    // Calling for virtual void Component::handleEvent(const MouseButtonEvent&)
    Component::handleEvent(e); 


    ... // doing other stuff
}


void MyComponent::handleEvent(const KeyEvent& e)
{
     // Calling for virtual void Component::handleEvent(const KeyEvent&)
     Component::handleEvent(e);

     ... // doing stuff
}

Somewhere else:


...

Component* c = myComponentPointer;

MouseButtonEvent e = {};
e.point = {10, 20};
e.foo = "bar";

// Just call virtual overloaded function here to be as simple from the side as possible
// Calling here virtual void MyComponent::handleEvent(const MouseButtonEvent&);
c->handleEvent(e);

...

Question

I want to save this calling pattern when I nicely just call virtual functions that are implemented for end nodes of event heirarchy. But I want to put some code inside Component::handleEvent for each of this virtual functions that just needs neither end nodes nor root node event. How can I rewrite template<typename GMouseEvent> void handleMouseEvent(const GMouseEvent& e) and template<typename GNonMouseEvent> void handleNonMouseEvent(const GMouseNonEvent& e)?


Solution

  • I found a way to do what I want.

    Component.h:

    
    class Component
    {
        ...
    
        template<typename GMouseEvent>
        ShouldParentHandleEvent handleMouseEvent(const GMouseEvent& e)
        {
            if (!children.empty())
            {
                /* 
                   There is a trick: I cast unknown const reference to const MouseEvent&
                   to be able then read it as MouseEvent. I also need a copy of the original
                   event. So I copy it and cast it to a reference of MouseEvent&.
                */
                const auto& eMouseEvent = static_cast<const MouseEvent&>(e);
                auto eCopy = e;
    
                auto& eMouse = static_cast<MouseEvent&>(eCopy);
    
                for (const auto& child : children)
                {
                    /* 
                        There magic happens: eMouse is a reference to eCopy (original event).
                        We need it just only to write. Then we need to preserve event type
                        when we will call child->handleEvent(eCopy) with modified event of the
                        original type. So the right overloaded virtual function is called.
                    */
                    eMouse.point = child->transformToLocalPoint(eMouseEvent.point);
                    if (child->predicateOnMouseEventProperty(eMouseEvent.point))
                    {
                        return child->handleEvent(eCopy);
                    }
                }
            }
            return true;
        }
    
        template<typename GNonMouseEvent>
        ShouldParentHandleEvent handleNonMouseEvent(const GNonMouseEvent& e)
        {
            bool shouldComponentHandleEvent = true;
    
            for (const auto& child : children)
                shouldComponentHandleEvent = child->handleEvent(e) && shouldComponentHandleEvent;
    
            return shouldComponentHandleEvent;
        }
    
        ...
    }