Implementation of Observable with multiple callback function signatures
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(); 
}