Implementation of Observable with multiple callback function signatures I have been playing around with ways of implementing a generic class for supporting the observer pattern. I have already asked to review a basic approach here. Now I have come up with an Observable class that supports parametrizing with an arbitrary number of function signatures, in order for the subject class to register and notify observers interested in different kinds of events that require different kind of information to be transmitted.

Here is the code:

observable.h:

#include &lt;functional&gt;
#include &lt;vector&gt;
#include &lt;tuple&gt;

template&lt;typename ... Funcs&gt;
class Observable
{
public:
    using ObserverList = std::tuple&lt;std::vector&lt;std::function&lt;Funcs&gt;&gt;...&gt;;

    template&lt;unsigned int eventidx, typename Callable&gt;
    void AttachObserver(Callable c)
    {
        std::get&lt;eventidx&gt;(observers).push_back(c);
    }

    template&lt;unsigned int eventidx, typename ... T&gt;
    void NotifyObserversOfEvent(T ... args)
    {
        for(auto f: std::get&lt;eventidx&gt;(observers))
        {
            f(std::forward&lt;T&gt;(args)...);
        }
    }

private:
    ObserverList observers;
};


Sample Test code

main.cpp:

#include &lt;iostream&gt;
#include &lt;functional&gt;
#include &lt;vector&gt;

#include "observable.h"

class A : public Observable&lt;
                            void(void),
                            void(int,std::string),
                            void(int,int),
                            &gt;
{
public:
    enum Event : unsigned
    {
        Event0,
        Event1,
        Event2
    };

private:
    int a;
    std::string s;
};

class B
{
public:
    void EventZeroHappened()
    {
        std::cout &lt;&lt; "B has observed event zero\n";
    }


    void EventOneHappened(int a, std::string s)
    {
        std::cout &lt;&lt; "B has observed event one with params: " &lt;&lt; a &lt;&lt; " and " &lt;&lt; "\"" &lt;&lt; s &lt;&lt; "\"" &lt;&lt;'\n';
    }

    void EventTwoHappened(int a, int b)
    {
        std::cout &lt;&lt; "B has observed event two with params: " &lt;&lt; a &lt;&lt; " and " &lt;&lt; b &lt;&lt;'\n';
    }
};




int main()
{
    A a;
    B b;

    a.AttachObserver&lt;A::Event::Event0&gt;([&amp;b](){b.EventZeroHappened();});
    a.AttachObserver&lt;A::Event::Event1&gt;([&amp;b](int i, std::string s){b.EventOneHappened(i,s);});
    a.AttachObserver&lt;A::Event::Event2&gt;([&amp;b](int i, int j){b.EventTwoHappened(i,j);});

    a.NotifyObserversOfEvent&lt;A::Event::Event0&gt;();
    a.NotifyObserversOfEvent&lt;A::Event::Event1&gt;(37,"Hello There");
    a.NotifyObserversOfEvent&lt;A::Event::Event2&gt;(182,150);
}


Basically the idea I wanted to implement is that the user of the observable class, simply writes in the template parameter list, the function signatures of the callback it supports. Instead of forcing the observer to implement an abstract interface and derive from it, which pollutes the observer code and creates an unnecessary dependency. The only draw back is that in order to select which event the observer wants to subscribe to (and which event the subject wants to transmit) it has to be done via integral constant, which is made a bit nicer in the example with an enum. I'm still searching for a better way to do this part.

The reason behind that is that, even in the old way of implementing observer, where you hard-code in a *.h file a pure abstract class and tell other classes that want to observe: "please overload this virtual functions if you want to observe me". And the meaning of each of the events you are going to notify is encoded in the names of the functions. I thought this example was a more general and flexible way of transmitting the same information. Plus being a bit easier to modify to add new events.

As always, I would welcome any comments, corrections criticisms and suggestions.
There is a way to get rid of the integral constant dispatch without forcing the user to inherit from an observable class, but it's a bit complex.

The idea is to encapsulate runtime polymorphism inside the Observable class. The pattern looks like:

class PolymorphicBehavior {
    class Concept {
        // here are the virtual functions
    };
    template &lt;typename ConcreteType&gt;
    class Object {
        // here are concrete implementations of the functions
        // The class encapsulates type information
        private:
        ConcreteType value;
    };
    public: 
    template &lt;typename ConcreteType&gt;
    PolymorphicBehavior(const ConcreteType&amp; ct) : ptr(new Object&lt;ConcreteType&gt;(ct)) {}
    private:
    Concept* ptr;
};


In the case of Observable, this pattern allows you to erase the function signature (concrete type information is stored in the inner object). There's still another type erasure to perform though, because we'll need to pass arguments to the encapsulated functions. I used std::any over a tuple to create a unified interface.

Here's the full code:

#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;memory&gt;
#include &lt;tuple&gt;
#include &lt;any&gt;
#include &lt;functional&gt;

//deducing the tuple type from the function type
// e.g: std::function&lt;void(float, double)&gt; =&gt; std::tuple&lt;float, double&gt;
template &lt;typename T&gt;
struct tuple_desc;
template &lt;typename R, typename... Args&gt;
struct tuple_desc&lt;std::function&lt;R(Args...)&gt;&gt; {
    using type = std::tuple&lt;Args...&gt;;
};

// deducing the function signature from the callable
template &lt;typename Callable&gt;
using function_class = decltype(std::function(std::declval&lt;Callable&gt;()));

class Observable {

    class Observable_model { // type neutral hook
        public:
        virtual void notify(unsigned event_number, std::any&amp; argument_wrapper) = 0;
        virtual ~Observable_model() = default;
    };

    template &lt;typename Callback&gt;
    class Observable_object : public Observable_model { // stores concrete type information
        public:
        Observable_object(unsigned event_class, const Callback&amp; cbk) : event_class(event_class), callback(cbk) {}

        void notify(unsigned event_number, std::any&amp; argument_wrapper) override {
            using Arg_tuple = typename tuple_desc&lt;decltype(this-&gt;callback)&gt;::type; // so tuple&lt;int, float&gt; for [](int a, float b) { /*...*/ }
            if (event_number == event_class) { // dynamic dispatch
                std::apply(callback, std::any_cast&lt;Arg_tuple&gt; (argument_wrapper));
            }
        }
        private:
        unsigned event_class;
        function_class&lt;Callback&gt; callback;
    };

    public:
    template &lt;typename Callback&gt;
    void attach(unsigned event_class, Callback&amp;&amp; callback) {
        auto new_obj = std::make_unique&lt;Observable_object&lt;Callback&gt;&gt;(event_class, callback);
        observations.push_back(std::move(new_obj));
    }
    template &lt;typename... Args&gt;
    void notify(unsigned event_number, Args&amp;&amp;... args) {
        std::tuple&lt;Args...&gt; arg_tuple(std::forward&lt;Args&gt;(args)...);
        std::any argument_wrapper(arg_tuple); // argument type erasure
        for (auto&amp; obs : observations) obs-&gt;notify(event_number, argument_wrapper);
    }

    private:
    std::vector&lt;std::unique_ptr&lt;Observable_model&gt;&gt; observations;

};

// testing the interface;

enum { WILDLIFE = 0, MACHINES = 1 };

struct Buzzer {
    void go(int h) { std::cout &lt;&lt; "drrrr... drrrr... it's " &lt;&lt; h &lt;&lt; " a.m, time to wake up!\n"; }
};
struct Coffee_machine {
    void make_coffee(int h) { if (h == 7) std::cout &lt;&lt; "time to make coffee!\n"; else std::cout &lt;&lt; "no coffee yet baby!\n"; }
};

struct Rooster {
    void sing() { std::cout &lt;&lt; "Cocorico!\n"; }
};
struct Vampire {
    void bury() { std::cout &lt;&lt; "Bye! I'll be in my grave!\n"; }
};

struct Sun : public Observable {
    void rise() {
        std::cout &lt;&lt; "Waouh, the sun rises!\n";
        notify(WILDLIFE);
        notify(MACHINES, 6);
    }
};

int main() {   
    Sun sun;
    Buzzer buzzer;
    Rooster rooster;
    Vampire vampire;
    Coffee_machine coffee_machine;
    sun.attach(WILDLIFE, [&amp;rooster] {rooster.sing();});
    sun.attach(WILDLIFE, [&amp;vampire] {vampire.bury();});
    sun.attach(MACHINES, [&amp;buzzer](int h) {buzzer.go(h);});
    sun.attach(MACHINES, [&amp;coffee_machine](int h) {coffee_machine.make_coffee(h);});
    sun.rise(); 
}