jjmcc
jjmcc

Reputation: 875

What is an alternative to using templates on a virtual member function?

I am creating a simple event system where multiple listeners can be notified on a specific topic and when an event is fired, it can pass a generic payload to the event, and the listeners will match the format of the fired event. However, because it's not possible to use templates on a virtual function, how else can I achieve this?

class AEventListener
{
public:

    template<class T>
    struct PayloadObject {
        T obj;
    };

    explicit AEventListener();
    virtual ~AEventListener();

//error here because T is undefined. Each PayloadObject may have a different type
    virtual void notify(vector<shared_ptr<PayloadObject<T>>> payload) = 0;
};

The notify method is called when an event topic has a listener subscribed, but I want a generic way of just passing a load of random objects to the listener.

For example

fireEvent("test.topic", Payload { 0, "hello", 123 });
//...
listener.notify(payload);

How would I go about this in C++?


I have managed to get around this, although I don't think this is the best way and could slow down performance.

template<class T>
struct PayloadObject : public APayloadObject {
    T obj;

    PayloadObject(T obj) {
        this->obj = obj;
    }

    ~PayloadObject() override {

    };

};

struct APayloadObject {
    virtual ~APayloadObject();
};

Firing:

vector<shared_ptr<APayloadObject>> payload;
payload.push_back(shared_ptr<PayloadObject<int>>(new PayloadObject<int>(5))); //payload[0] = int - 5
Events::fire(EventKeys::DISCONNECTION_EVENT, payload);

Notifying:

shared_ptr<PayloadObject<int>> number = dynamic_pointer_cast<PayloadObject<int>>(payload[0]);
int id = number.get()->obj; //payload[0] = int - 5

Upvotes: 3

Views: 395

Answers (1)

Chris Beck
Chris Beck

Reputation: 16204

One simple approach is to come up with a common base or common interface for the Payload objects. So that they are not a template class.

struct Payload {
  virtual ~Payload() = default;
  virtual std::string foo() const;
  virtual std::string bar() const;
};

Another way is to use a variant type for the payload objects:

using Message_t = boost::variant<A, B, C>;

and then make AEventListener take the Message_t type so that it doesn't require the member function to be a template.

class AEventListener
{
public:
    virtual ~AEventListener();

    virtual void notify(std::vector<Message_t> payload) = 0;
};

In C++17 you could use std::variant for this instead of boost.

Yet another way is to skip using a variant, and just make it so that the Listener must implement three different functions, one for each type:

class AEventListener
{
public:
    virtual ~AEventListener();

    virtual void notifyA(A payload) = 0;
    virtual void notifyB(B payload) = 0;
    virtual void notifyC(C payload) = 0;
};

More generally, it is pretty difficult in C++ to make a concept like "Function object that is callable with any particular type of arguments". This is in part because... it is not very useful, there is not much that you can do generically with data of ANY type that you can assume nothing about.

So I would suggest that you think hard about refining your Event Listener concept, and make more concrete what it is that objects of this type are ACTUALLY supposed to be required to do.

Upvotes: 2

Related Questions