#include <typeindex>
#include <unordered_map>

struct PBase {virtual ~PBase() noexcept = default;};
struct P1 : public PBase {};
struct P2 : public PBase {};

class SBase
{
public:
    void doIt(PBase *base)
    {
        internalDoIt(base);
    }

protected:
    virtual void internalDoIt(PBase *base) = 0;
};

using callback_type = void(*)(SBase*, PBase*);
using map_type = std::unordered_map<std::type_index, callback_type>;

template <typename P, callback_type Fnc>
struct pair_type {};

template <class ... Pairs>
class BasicS : public SBase
{
protected:
    void internalDoIt(PBase *base) override
    {
        map_[typeid(*base)](this, base);
    }

private:
    static map_type map_;
};

namespace detail
{
    template <class ... Pairs>
    struct map_getter;

    template <typename P, callback_type F>
    struct map_getter<pair_type<P, F>>
    {
        static map_type get()
        {
            return map_type{{typeid(P), F}};
        }
    };

    template <typename P, callback_type F, class ... Tail>
    struct map_getter<pair_type<P, F>, Tail...>
    {
        static map_type get()
        {
            auto m = map_getter<Tail...>::get();
            m.emplace(typeid(P), F);
            return m;
        }
    };
}

template <class ... Pairs>
map_type BasicS<Pairs...>::map_  = detail::map_getter<Pairs...>::get();

#include <iostream>

void s1p1DoIt(SBase*, PBase*)
{
    std::cout << "S1 P1\n";
}

void s1p2DoIt(SBase*, PBase*)
{
    std::cout << "S1 P2\n";
}

class S1 : public BasicS<pair_type<P1, s1p1DoIt>, pair_type<P2, s1p2DoIt>> {};

void s2p1DoIt(SBase*, PBase*)
{
    std::cout << "S2 P1\n";
}

void s2p2DoIt(SBase*, PBase*)
{
    std::cout << "S2 P2\n";
}

class S2 : public BasicS<pair_type<P1, s2p1DoIt>, pair_type<P2, s2p2DoIt>> {};

int main()
{
    S1 s1;
    S2 s2;
    P1 p1;
    P2 p2;

    SBase &sbase1 = s1;
    SBase &sbase2 = s2;
    PBase &pbase1 = p1;
    PBase &pbase2 = p2;

    sbase1.doIt(&pbase1);
    sbase1.doIt(&pbase2);
    sbase2.doIt(&pbase1);
    sbase2.doIt(&pbase2);
}
