Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ function does not accept concrete implementation

I am trying to implement a typesafe event bus. I am stuck with the EventBus::subscribe function because it does not accept my concrete event handler. In an earlier version I had my AbstractEventHandler implemented as an abstract class only, without it being a template. I had no problem with that implementation. That's why I assume that the actual issue is with the abstract template.

The code below is a stripped down version of my implementation. The first block consist of the "skeleton" of the event bus and its required classes whereas the second block shows an actual implementation of the event, event handler and the main.

The enum holds all the different events available. The abstract event is the base of which all concrete events are derived of. The event handler is an abstract template with an event as template class to ensure type-safety. The event bus is responsible for distributing all published events to its respective handlers.

enum EVENT_TYPE 
{
    ON_EVENT_1,
    ON_EVENT_2
};

class AbstractEvent 
{
public:
    AbstractEvent() {};
    virtual ~AbstractEvent() {};

    virtual EVENT_TYPE type() = 0;
};

template<class T>
class AbstractEventHandler 
{
public:
    AbstractEventHandler() {};
    virtual ~AbstractEventHandler() {};

    virtual void on_event(T *event) = 0;
};

class EventBus 
{
public:
    EventBus() {};
    virtual ~EventBus() {};

    void subscribe(EVENT_TYPE type, 
                    AbstractEventHandler<AbstractEvent> *eventHandler) {
        // Add handler to vector for further use
    }

    void publish(AbstractEvent *event) {
        // send event to each handler in respective vector
    }
};

Below are my concrete event and event handler and the main()

class ConcreteEvent : public AbstractEvent 
{
public:
    ConcreteEvent() {};
    virtual ~ConcreteEvent() {};

    EVENT_TYPE type() {
        return ON_EVENT_1;
    };
};

class ConcreteEventHandler : public AbstractEventHandler<ConcreteEvent> 
{
public:
    ConcreteEventHandler() {}
    virtual ~ConcreteEventHandler() {};

    void on_event(ConcreteEvent *event) {
        // Do something
    };
};

int main() 
{
    EventBus *eventBus = new EventBus();

    ConcreteEventHandler handler = ConcreteEventHandler();

    // This failes!
    eventBus->subscribe(ON_EVENT_1, &handler);
}

The compiler returns with an error saying that there is no matching function for call to

EventBus::subscribe(EVENT_TYPE, ConcreteEventHandler*) 

and that the only candidates are

void EventBus::subscribe(EVENT_TYPE, AbstractEventHandler<AbstractEvent>*) 

How can I implement my EventBus::subscribe method to accept concrete implementations of my abstract class?

Update: Solution

I have changed the method description of EventBus::subscribe to the following and it now works nicely:

template<typename T>
void subscribe(EVENT_TYPE type, AbstractEventHandler<T> *eventHandler) {

}

Thanks, Rohan, for your hints! They helped me to find this solution.

like image 663
Fabian Avatar asked Apr 02 '12 08:04

Fabian


1 Answers

The reason is because, ConcreteEventHandler is a subclass of AbstractEventHandler<ConcreteEvent> and not AbstractEventHandler<AbstractEvent>.

This might seem surprising, but AbstractEventHandler<ConcreteEvent> cannot be a subclass of AbstractEventHandler<AbstractEvent> even though ConcreteEvent is a subclass of AbstractEvent.

The reason is because, with templates, templating as you wish does not guarantee type safety. Let us look at an example. Let's go over the standard paradigm of a base-class Animal and sub-classes Cat and Dog. Let's say we have a list of Animals:

std::list<Animals>* animals;

and a list of cats:

std::list<Cat> cats;

The following, is NOT a valid cast:

animals = &cats;

The reason is, because, if I am to do this,

animals->add(new Dog("Ben"));

I would actually add a Dog to a list of Cats. cats.last() here would actually return a Dog. So, in this case, you are essentially adding a Dog to a list of Cats. I've seen enough Looney Tunes episodes to know that this is not a good idea:

cats.last().meow();

The above is definitely not true, as we all know that a Dog can only bowbow().

EDIT

To answer your question, here is what I suggest you do; Let ConcreteEventHandler inherit from AbstractEventHandler<AbstractEvent>, and within the code, wherever you use a ConcreteEvent, use a dynamic_case to cast the AbstractEvent to a ConcreteEvent. This will use run-time introspection, which might impact performance a little (also I have seen quite a few people opposed to using a dynamic cast), but you will be able to successfully perform a valid upcast of the datatype.

like image 168
Rohan Prabhu Avatar answered Sep 29 '22 03:09

Rohan Prabhu