Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding multiple virtual functions in a variadic class template

Let me just give a code example.

#include <iostream>
#include <utility>
#include <tuple>

template <typename Service>
struct SubscriberImpl {
    virtual void handleService(Service const&) = 0;
};

template <typename...ServiceType>
struct Subscriber : SubscriberImpl<ServiceType>... {

};

struct IntService {};
struct FloatService {};

template <typename StatusUpdatePolicy, typename... ServiceType>
struct StatusUpdater : Subscriber<ServiceType...>
{
    StatusUpdater(StatusUpdatePolicy const& statusUpdater)
    : m_statusUpdater{statusUpdater}
    {}
    // wont work
    void handleService(IntService const& service) override {
        m_statusUpdater.updateService(service);
    }

    void handleService(FloatService const& service) override {
        m_statusUpdater.updateService(service);
    }

    StatusUpdatePolicy m_statusUpdater;
}; 

struct DummyPolicy {
    void updateService(IntService const& service) {
        m_i = 42;
        std::cout << m_i << "\n";
    }

    void updateService(FloatService const& service) {
        m_f = 3.14f;
        std::cout << m_f << "\n";
    }

    int m_i;
    float m_f;
};
int main() {

    StatusUpdater<DummyPolicy, IntService, FloatService> su(DummyPolicy{}); 

    su.handleService(IntService{});
    su.handleService(FloatService{});
}

Here Subscriber has a pure virtual function handleService(ServiceType const) for each template parameter in pack ServiceType.... So I have to override each one on StatusUpdater. Here, I have provided the ones I need by hand for IntService and FloatService, knowing I will be only needing those in this minimal example. But I want to be able to provide an override for whatever there is in the pack ServiceType.... All of them will call updateService method of the given policy anyways.

Please note that Subscriber comes from an external library and I cannot modify its definition.

like image 316
meguli Avatar asked Mar 01 '19 09:03

meguli


2 Answers

You cannot put such implementations directly into the class, you have to inherit them (similarly to how Subscriber inherits from multiple SubscriberImpl instantiations). However, to override them all and still keep your class polymorphically usable as a Subscriber, you will have to inherit them "sequentially" instead of "in parallel." Additionally, the Curiously recurring template pattern can be used to give all the implementations access to the final overriding object:

template <class Self, class SubscriberClass, class... ServiceTypes>
struct StatusUpdaterOverride;


template <class Self, class SubscriberClass, class ThisType, class... RemainingTypes>
struct StatusUpdaterOverride<Self, SubscriberClass, ThisType, RemainingTypes...> : StatusUpdaterOverride<Self, SubscriberClass, RemainingTypes...>
{
  void handleService(ThisType const& service) override
  {
    static_cast<Self*>(this)->m_statusUpdater.updateService(service);
  }
  using StatusUpdaterOverride<Self, SubscriberClass, RemainingTypes...>::handleService;
};


template <class Self, class SubscriberClass, class ThisType>
struct StatusUpdaterOverride<Self, SubscriberClass, ThisType> : SubscriberClass
{
  void handleService(ThisType const& service) override
  {
    static_cast<Self*>(this)->m_statusUpdater.updateService(service);
  }
};


template <class StatusUpdatePolicy, class... ServiceType>
struct StatusUpdater : StatusUpdaterOverride<StatusUpdater<StatusUpdatePolicy, ServiceType...>, Subscriber<ServiceType...>, ServiceType...>
{
    StatusUpdater(StatusUpdatePolicy const& statusUpdater)
    : m_statusUpdater{statusUpdater}
    {}

    StatusUpdatePolicy m_statusUpdater;
};

[Live example]

like image 95
Angew is no longer proud of SO Avatar answered Nov 04 '22 16:11

Angew is no longer proud of SO


I can't see a solution to do exactly what you want. However you can achieve the same behavior without needing the virtuality at all. I initially thought about a CRTP solution just like @Angew's answer and then came up with another possibility:

You could edit your Subscriber class like this:

template <typename ServiceType>
class Subscriber {
public:
    template <typename Handler>
    void handleService(ServiceType const& service, Handler&& hdler) {
        // Maybe give `updateService` a broader name that can extend to other service handlers
        std::forward<Handler>(hdler).updateService(service);
    }
};

With that, your client code becomes:

template <typename StatusUpdatePolicy, typename... ServiceType>
struct StatusUpdater : Subscriber<ServiceType>...
{
    StatusUpdater(StatusUpdatePolicy const& statusUpdater)
    : m_statusUpdater{statusUpdater}
    {}
    template <typename ServiceT>
    void handleService(ServiceT const& service) override {
        Subscriber<ServiceT>::handleService(service, m_statusUpdater);
    }

    StatusUpdatePolicy m_statusUpdater;
}; 
like image 33
Rerito Avatar answered Nov 04 '22 18:11

Rerito