Implementation of Observable with multiple callback function signatures

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
3
down vote

favorite












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 <functional>
#include <vector>
#include <tuple>

template<typename ... Funcs>
class Observable

public:
using ObserverList = std::tuple<std::vector<std::function<Funcs>>...>;

template<unsigned int eventidx, typename Callable>
void AttachObserver(Callable c)

std::get<eventidx>(observers).push_back(c);


template<unsigned int eventidx, typename ... T>
void NotifyObserversOfEvent(T ... args)

for(auto f: std::get<eventidx>(observers))

f(std::forward<T>(args)...);



private:
ObserverList observers;
;


Sample Test code



main.cpp:



#include <iostream>
#include <functional>
#include <vector>

#include "observable.h"

class A : public Observable<
void(void),
void(int,std::string),
void(int,int),
>

public:
enum Event : unsigned

Event0,
Event1,
Event2
;

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

class B

public:
void EventZeroHappened()

std::cout << "B has observed event zeron";



void EventOneHappened(int a, std::string s)

std::cout << "B has observed event one with params: " << a << " and " << """ << s << """ <<'n';


void EventTwoHappened(int a, int b)

std::cout << "B has observed event two with params: " << a << " and " << b <<'n';

;




int main()

A a;
B b;

a.AttachObserver<A::Event::Event0>([&b]()b.EventZeroHappened(););
a.AttachObserver<A::Event::Event1>([&b](int i, std::string s)b.EventOneHappened(i,s););
a.AttachObserver<A::Event::Event2>([&b](int i, int j)b.EventTwoHappened(i,j););

a.NotifyObserversOfEvent<A::Event::Event0>();
a.NotifyObserversOfEvent<A::Event::Event1>(37,"Hello There");
a.NotifyObserversOfEvent<A::Event::Event2>(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.







share|improve this question

























    up vote
    3
    down vote

    favorite












    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 <functional>
    #include <vector>
    #include <tuple>

    template<typename ... Funcs>
    class Observable

    public:
    using ObserverList = std::tuple<std::vector<std::function<Funcs>>...>;

    template<unsigned int eventidx, typename Callable>
    void AttachObserver(Callable c)

    std::get<eventidx>(observers).push_back(c);


    template<unsigned int eventidx, typename ... T>
    void NotifyObserversOfEvent(T ... args)

    for(auto f: std::get<eventidx>(observers))

    f(std::forward<T>(args)...);



    private:
    ObserverList observers;
    ;


    Sample Test code



    main.cpp:



    #include <iostream>
    #include <functional>
    #include <vector>

    #include "observable.h"

    class A : public Observable<
    void(void),
    void(int,std::string),
    void(int,int),
    >

    public:
    enum Event : unsigned

    Event0,
    Event1,
    Event2
    ;

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

    class B

    public:
    void EventZeroHappened()

    std::cout << "B has observed event zeron";



    void EventOneHappened(int a, std::string s)

    std::cout << "B has observed event one with params: " << a << " and " << """ << s << """ <<'n';


    void EventTwoHappened(int a, int b)

    std::cout << "B has observed event two with params: " << a << " and " << b <<'n';

    ;




    int main()

    A a;
    B b;

    a.AttachObserver<A::Event::Event0>([&b]()b.EventZeroHappened(););
    a.AttachObserver<A::Event::Event1>([&b](int i, std::string s)b.EventOneHappened(i,s););
    a.AttachObserver<A::Event::Event2>([&b](int i, int j)b.EventTwoHappened(i,j););

    a.NotifyObserversOfEvent<A::Event::Event0>();
    a.NotifyObserversOfEvent<A::Event::Event1>(37,"Hello There");
    a.NotifyObserversOfEvent<A::Event::Event2>(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.







    share|improve this question





















      up vote
      3
      down vote

      favorite









      up vote
      3
      down vote

      favorite











      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 <functional>
      #include <vector>
      #include <tuple>

      template<typename ... Funcs>
      class Observable

      public:
      using ObserverList = std::tuple<std::vector<std::function<Funcs>>...>;

      template<unsigned int eventidx, typename Callable>
      void AttachObserver(Callable c)

      std::get<eventidx>(observers).push_back(c);


      template<unsigned int eventidx, typename ... T>
      void NotifyObserversOfEvent(T ... args)

      for(auto f: std::get<eventidx>(observers))

      f(std::forward<T>(args)...);



      private:
      ObserverList observers;
      ;


      Sample Test code



      main.cpp:



      #include <iostream>
      #include <functional>
      #include <vector>

      #include "observable.h"

      class A : public Observable<
      void(void),
      void(int,std::string),
      void(int,int),
      >

      public:
      enum Event : unsigned

      Event0,
      Event1,
      Event2
      ;

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

      class B

      public:
      void EventZeroHappened()

      std::cout << "B has observed event zeron";



      void EventOneHappened(int a, std::string s)

      std::cout << "B has observed event one with params: " << a << " and " << """ << s << """ <<'n';


      void EventTwoHappened(int a, int b)

      std::cout << "B has observed event two with params: " << a << " and " << b <<'n';

      ;




      int main()

      A a;
      B b;

      a.AttachObserver<A::Event::Event0>([&b]()b.EventZeroHappened(););
      a.AttachObserver<A::Event::Event1>([&b](int i, std::string s)b.EventOneHappened(i,s););
      a.AttachObserver<A::Event::Event2>([&b](int i, int j)b.EventTwoHappened(i,j););

      a.NotifyObserversOfEvent<A::Event::Event0>();
      a.NotifyObserversOfEvent<A::Event::Event1>(37,"Hello There");
      a.NotifyObserversOfEvent<A::Event::Event2>(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.







      share|improve this question











      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 <functional>
      #include <vector>
      #include <tuple>

      template<typename ... Funcs>
      class Observable

      public:
      using ObserverList = std::tuple<std::vector<std::function<Funcs>>...>;

      template<unsigned int eventidx, typename Callable>
      void AttachObserver(Callable c)

      std::get<eventidx>(observers).push_back(c);


      template<unsigned int eventidx, typename ... T>
      void NotifyObserversOfEvent(T ... args)

      for(auto f: std::get<eventidx>(observers))

      f(std::forward<T>(args)...);



      private:
      ObserverList observers;
      ;


      Sample Test code



      main.cpp:



      #include <iostream>
      #include <functional>
      #include <vector>

      #include "observable.h"

      class A : public Observable<
      void(void),
      void(int,std::string),
      void(int,int),
      >

      public:
      enum Event : unsigned

      Event0,
      Event1,
      Event2
      ;

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

      class B

      public:
      void EventZeroHappened()

      std::cout << "B has observed event zeron";



      void EventOneHappened(int a, std::string s)

      std::cout << "B has observed event one with params: " << a << " and " << """ << s << """ <<'n';


      void EventTwoHappened(int a, int b)

      std::cout << "B has observed event two with params: " << a << " and " << b <<'n';

      ;




      int main()

      A a;
      B b;

      a.AttachObserver<A::Event::Event0>([&b]()b.EventZeroHappened(););
      a.AttachObserver<A::Event::Event1>([&b](int i, std::string s)b.EventOneHappened(i,s););
      a.AttachObserver<A::Event::Event2>([&b](int i, int j)b.EventTwoHappened(i,j););

      a.NotifyObserversOfEvent<A::Event::Event0>();
      a.NotifyObserversOfEvent<A::Event::Event1>(37,"Hello There");
      a.NotifyObserversOfEvent<A::Event::Event2>(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.









      share|improve this question










      share|improve this question




      share|improve this question









      asked Jan 14 at 22:31









      bone

      1264




      1264




















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          1
          down vote













          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 <typename ConcreteType>
          class Object
          // here are concrete implementations of the functions
          // The class encapsulates type information
          private:
          ConcreteType value;
          ;
          public:
          template <typename ConcreteType>
          PolymorphicBehavior(const ConcreteType& ct) : ptr(new Object<ConcreteType>(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 <iostream>
          #include <vector>
          #include <memory>
          #include <tuple>
          #include <any>
          #include <functional>

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

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

          class Observable

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

          template <typename Callback>
          class Observable_object : public Observable_model // stores concrete type information
          public:
          Observable_object(unsigned event_class, const Callback& cbk) : event_class(event_class), callback(cbk)

          void notify(unsigned event_number, std::any& argument_wrapper) override
          using Arg_tuple = typename tuple_desc<decltype(this->callback)>::type; // so tuple<int, float> for (int a, float b) /*...*/
          if (event_number == event_class) // dynamic dispatch
          std::apply(callback, std::any_cast<Arg_tuple> (argument_wrapper));


          private:
          unsigned event_class;
          function_class<Callback> callback;
          ;

          public:
          template <typename Callback>
          void attach(unsigned event_class, Callback&& callback)
          auto new_obj = std::make_unique<Observable_object<Callback>>(event_class, callback);
          observations.push_back(std::move(new_obj));

          template <typename... Args>
          void notify(unsigned event_number, Args&&... args)
          std::tuple<Args...> arg_tuple(std::forward<Args>(args)...);
          std::any argument_wrapper(arg_tuple); // argument type erasure
          for (auto& obs : observations) obs->notify(event_number, argument_wrapper);


          private:
          std::vector<std::unique_ptr<Observable_model>> observations;

          ;

          // testing the interface;

          enum WILDLIFE = 0, MACHINES = 1 ;

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

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

          struct Sun : public Observable
          void rise()
          std::cout << "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, [&rooster] rooster.sing(););
          sun.attach(WILDLIFE, [&vampire] vampire.bury(););
          sun.attach(MACHINES, [&buzzer](int h) buzzer.go(h););
          sun.attach(MACHINES, [&coffee_machine](int h) coffee_machine.make_coffee(h););
          sun.rise();






          share|improve this answer





















          • Damn! I'll need some time to read this thoroughly before I can understand what it's doing. Thanks for taking the time to respond!
            – bone
            Jan 16 at 14:20










          • Edit: So I tried your code, but I changed the last attach to: sun.attach(MACHINES, [&coffee_machine](int h, int i) coffee_machine.make_coffee(h+i);); And it fails at runtime with a bad_any_cast exception. My idea was to have the events be tied to a specific function signature at compile time. Your idea is good, but it is not exactly what I am intending.
            – bone
            Jan 16 at 15:35










          • Sure it fails at runtime. Machines is the equivalent of one of your events and is tied to a signature with only one int. There's a mapping from a run time integer (an enum in my code to be more expressive) to a function signature.
            – papagaga
            Jan 16 at 20:36










          • Yes I see. IF I understand it correctly, the problem with your code is that the first observer calling attach will bind the event to the signature of its choosing. I wanted the subject class inheriting from Observable to be able to configure the association of event to function signature, and force observers to abide to that, or have compile error if they use it wrong.
            – bone
            Jan 16 at 22:01










          • The observable class is doing the configuring via the attachmethod. The observers are regular lambdas or functions, nothing fancy which could bind events and signatures. As to compile time errors, you're right, it's a good thing to strive for. It's a question of balance, because on the other hand your interface is quite restrictive (no run-time attaching lacks flexibility) and a bit ugly. I guess you could try and combine both approach in some way .
            – papagaga
            Jan 16 at 23:02










          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f185114%2fimplementation-of-observable-with-multiple-callback-function-signatures%23new-answer', 'question_page');

          );

          Post as a guest






























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          up vote
          1
          down vote













          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 <typename ConcreteType>
          class Object
          // here are concrete implementations of the functions
          // The class encapsulates type information
          private:
          ConcreteType value;
          ;
          public:
          template <typename ConcreteType>
          PolymorphicBehavior(const ConcreteType& ct) : ptr(new Object<ConcreteType>(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 <iostream>
          #include <vector>
          #include <memory>
          #include <tuple>
          #include <any>
          #include <functional>

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

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

          class Observable

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

          template <typename Callback>
          class Observable_object : public Observable_model // stores concrete type information
          public:
          Observable_object(unsigned event_class, const Callback& cbk) : event_class(event_class), callback(cbk)

          void notify(unsigned event_number, std::any& argument_wrapper) override
          using Arg_tuple = typename tuple_desc<decltype(this->callback)>::type; // so tuple<int, float> for (int a, float b) /*...*/
          if (event_number == event_class) // dynamic dispatch
          std::apply(callback, std::any_cast<Arg_tuple> (argument_wrapper));


          private:
          unsigned event_class;
          function_class<Callback> callback;
          ;

          public:
          template <typename Callback>
          void attach(unsigned event_class, Callback&& callback)
          auto new_obj = std::make_unique<Observable_object<Callback>>(event_class, callback);
          observations.push_back(std::move(new_obj));

          template <typename... Args>
          void notify(unsigned event_number, Args&&... args)
          std::tuple<Args...> arg_tuple(std::forward<Args>(args)...);
          std::any argument_wrapper(arg_tuple); // argument type erasure
          for (auto& obs : observations) obs->notify(event_number, argument_wrapper);


          private:
          std::vector<std::unique_ptr<Observable_model>> observations;

          ;

          // testing the interface;

          enum WILDLIFE = 0, MACHINES = 1 ;

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

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

          struct Sun : public Observable
          void rise()
          std::cout << "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, [&rooster] rooster.sing(););
          sun.attach(WILDLIFE, [&vampire] vampire.bury(););
          sun.attach(MACHINES, [&buzzer](int h) buzzer.go(h););
          sun.attach(MACHINES, [&coffee_machine](int h) coffee_machine.make_coffee(h););
          sun.rise();






          share|improve this answer





















          • Damn! I'll need some time to read this thoroughly before I can understand what it's doing. Thanks for taking the time to respond!
            – bone
            Jan 16 at 14:20










          • Edit: So I tried your code, but I changed the last attach to: sun.attach(MACHINES, [&coffee_machine](int h, int i) coffee_machine.make_coffee(h+i);); And it fails at runtime with a bad_any_cast exception. My idea was to have the events be tied to a specific function signature at compile time. Your idea is good, but it is not exactly what I am intending.
            – bone
            Jan 16 at 15:35










          • Sure it fails at runtime. Machines is the equivalent of one of your events and is tied to a signature with only one int. There's a mapping from a run time integer (an enum in my code to be more expressive) to a function signature.
            – papagaga
            Jan 16 at 20:36










          • Yes I see. IF I understand it correctly, the problem with your code is that the first observer calling attach will bind the event to the signature of its choosing. I wanted the subject class inheriting from Observable to be able to configure the association of event to function signature, and force observers to abide to that, or have compile error if they use it wrong.
            – bone
            Jan 16 at 22:01










          • The observable class is doing the configuring via the attachmethod. The observers are regular lambdas or functions, nothing fancy which could bind events and signatures. As to compile time errors, you're right, it's a good thing to strive for. It's a question of balance, because on the other hand your interface is quite restrictive (no run-time attaching lacks flexibility) and a bit ugly. I guess you could try and combine both approach in some way .
            – papagaga
            Jan 16 at 23:02














          up vote
          1
          down vote













          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 <typename ConcreteType>
          class Object
          // here are concrete implementations of the functions
          // The class encapsulates type information
          private:
          ConcreteType value;
          ;
          public:
          template <typename ConcreteType>
          PolymorphicBehavior(const ConcreteType& ct) : ptr(new Object<ConcreteType>(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 <iostream>
          #include <vector>
          #include <memory>
          #include <tuple>
          #include <any>
          #include <functional>

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

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

          class Observable

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

          template <typename Callback>
          class Observable_object : public Observable_model // stores concrete type information
          public:
          Observable_object(unsigned event_class, const Callback& cbk) : event_class(event_class), callback(cbk)

          void notify(unsigned event_number, std::any& argument_wrapper) override
          using Arg_tuple = typename tuple_desc<decltype(this->callback)>::type; // so tuple<int, float> for (int a, float b) /*...*/
          if (event_number == event_class) // dynamic dispatch
          std::apply(callback, std::any_cast<Arg_tuple> (argument_wrapper));


          private:
          unsigned event_class;
          function_class<Callback> callback;
          ;

          public:
          template <typename Callback>
          void attach(unsigned event_class, Callback&& callback)
          auto new_obj = std::make_unique<Observable_object<Callback>>(event_class, callback);
          observations.push_back(std::move(new_obj));

          template <typename... Args>
          void notify(unsigned event_number, Args&&... args)
          std::tuple<Args...> arg_tuple(std::forward<Args>(args)...);
          std::any argument_wrapper(arg_tuple); // argument type erasure
          for (auto& obs : observations) obs->notify(event_number, argument_wrapper);


          private:
          std::vector<std::unique_ptr<Observable_model>> observations;

          ;

          // testing the interface;

          enum WILDLIFE = 0, MACHINES = 1 ;

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

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

          struct Sun : public Observable
          void rise()
          std::cout << "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, [&rooster] rooster.sing(););
          sun.attach(WILDLIFE, [&vampire] vampire.bury(););
          sun.attach(MACHINES, [&buzzer](int h) buzzer.go(h););
          sun.attach(MACHINES, [&coffee_machine](int h) coffee_machine.make_coffee(h););
          sun.rise();






          share|improve this answer





















          • Damn! I'll need some time to read this thoroughly before I can understand what it's doing. Thanks for taking the time to respond!
            – bone
            Jan 16 at 14:20










          • Edit: So I tried your code, but I changed the last attach to: sun.attach(MACHINES, [&coffee_machine](int h, int i) coffee_machine.make_coffee(h+i);); And it fails at runtime with a bad_any_cast exception. My idea was to have the events be tied to a specific function signature at compile time. Your idea is good, but it is not exactly what I am intending.
            – bone
            Jan 16 at 15:35










          • Sure it fails at runtime. Machines is the equivalent of one of your events and is tied to a signature with only one int. There's a mapping from a run time integer (an enum in my code to be more expressive) to a function signature.
            – papagaga
            Jan 16 at 20:36










          • Yes I see. IF I understand it correctly, the problem with your code is that the first observer calling attach will bind the event to the signature of its choosing. I wanted the subject class inheriting from Observable to be able to configure the association of event to function signature, and force observers to abide to that, or have compile error if they use it wrong.
            – bone
            Jan 16 at 22:01










          • The observable class is doing the configuring via the attachmethod. The observers are regular lambdas or functions, nothing fancy which could bind events and signatures. As to compile time errors, you're right, it's a good thing to strive for. It's a question of balance, because on the other hand your interface is quite restrictive (no run-time attaching lacks flexibility) and a bit ugly. I guess you could try and combine both approach in some way .
            – papagaga
            Jan 16 at 23:02












          up vote
          1
          down vote










          up vote
          1
          down vote









          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 <typename ConcreteType>
          class Object
          // here are concrete implementations of the functions
          // The class encapsulates type information
          private:
          ConcreteType value;
          ;
          public:
          template <typename ConcreteType>
          PolymorphicBehavior(const ConcreteType& ct) : ptr(new Object<ConcreteType>(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 <iostream>
          #include <vector>
          #include <memory>
          #include <tuple>
          #include <any>
          #include <functional>

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

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

          class Observable

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

          template <typename Callback>
          class Observable_object : public Observable_model // stores concrete type information
          public:
          Observable_object(unsigned event_class, const Callback& cbk) : event_class(event_class), callback(cbk)

          void notify(unsigned event_number, std::any& argument_wrapper) override
          using Arg_tuple = typename tuple_desc<decltype(this->callback)>::type; // so tuple<int, float> for (int a, float b) /*...*/
          if (event_number == event_class) // dynamic dispatch
          std::apply(callback, std::any_cast<Arg_tuple> (argument_wrapper));


          private:
          unsigned event_class;
          function_class<Callback> callback;
          ;

          public:
          template <typename Callback>
          void attach(unsigned event_class, Callback&& callback)
          auto new_obj = std::make_unique<Observable_object<Callback>>(event_class, callback);
          observations.push_back(std::move(new_obj));

          template <typename... Args>
          void notify(unsigned event_number, Args&&... args)
          std::tuple<Args...> arg_tuple(std::forward<Args>(args)...);
          std::any argument_wrapper(arg_tuple); // argument type erasure
          for (auto& obs : observations) obs->notify(event_number, argument_wrapper);


          private:
          std::vector<std::unique_ptr<Observable_model>> observations;

          ;

          // testing the interface;

          enum WILDLIFE = 0, MACHINES = 1 ;

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

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

          struct Sun : public Observable
          void rise()
          std::cout << "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, [&rooster] rooster.sing(););
          sun.attach(WILDLIFE, [&vampire] vampire.bury(););
          sun.attach(MACHINES, [&buzzer](int h) buzzer.go(h););
          sun.attach(MACHINES, [&coffee_machine](int h) coffee_machine.make_coffee(h););
          sun.rise();






          share|improve this answer













          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 <typename ConcreteType>
          class Object
          // here are concrete implementations of the functions
          // The class encapsulates type information
          private:
          ConcreteType value;
          ;
          public:
          template <typename ConcreteType>
          PolymorphicBehavior(const ConcreteType& ct) : ptr(new Object<ConcreteType>(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 <iostream>
          #include <vector>
          #include <memory>
          #include <tuple>
          #include <any>
          #include <functional>

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

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

          class Observable

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

          template <typename Callback>
          class Observable_object : public Observable_model // stores concrete type information
          public:
          Observable_object(unsigned event_class, const Callback& cbk) : event_class(event_class), callback(cbk)

          void notify(unsigned event_number, std::any& argument_wrapper) override
          using Arg_tuple = typename tuple_desc<decltype(this->callback)>::type; // so tuple<int, float> for (int a, float b) /*...*/
          if (event_number == event_class) // dynamic dispatch
          std::apply(callback, std::any_cast<Arg_tuple> (argument_wrapper));


          private:
          unsigned event_class;
          function_class<Callback> callback;
          ;

          public:
          template <typename Callback>
          void attach(unsigned event_class, Callback&& callback)
          auto new_obj = std::make_unique<Observable_object<Callback>>(event_class, callback);
          observations.push_back(std::move(new_obj));

          template <typename... Args>
          void notify(unsigned event_number, Args&&... args)
          std::tuple<Args...> arg_tuple(std::forward<Args>(args)...);
          std::any argument_wrapper(arg_tuple); // argument type erasure
          for (auto& obs : observations) obs->notify(event_number, argument_wrapper);


          private:
          std::vector<std::unique_ptr<Observable_model>> observations;

          ;

          // testing the interface;

          enum WILDLIFE = 0, MACHINES = 1 ;

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

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

          struct Sun : public Observable
          void rise()
          std::cout << "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, [&rooster] rooster.sing(););
          sun.attach(WILDLIFE, [&vampire] vampire.bury(););
          sun.attach(MACHINES, [&buzzer](int h) buzzer.go(h););
          sun.attach(MACHINES, [&coffee_machine](int h) coffee_machine.make_coffee(h););
          sun.rise();







          share|improve this answer













          share|improve this answer



          share|improve this answer











          answered Jan 16 at 10:00









          papagaga

          2,799116




          2,799116











          • Damn! I'll need some time to read this thoroughly before I can understand what it's doing. Thanks for taking the time to respond!
            – bone
            Jan 16 at 14:20










          • Edit: So I tried your code, but I changed the last attach to: sun.attach(MACHINES, [&coffee_machine](int h, int i) coffee_machine.make_coffee(h+i);); And it fails at runtime with a bad_any_cast exception. My idea was to have the events be tied to a specific function signature at compile time. Your idea is good, but it is not exactly what I am intending.
            – bone
            Jan 16 at 15:35










          • Sure it fails at runtime. Machines is the equivalent of one of your events and is tied to a signature with only one int. There's a mapping from a run time integer (an enum in my code to be more expressive) to a function signature.
            – papagaga
            Jan 16 at 20:36










          • Yes I see. IF I understand it correctly, the problem with your code is that the first observer calling attach will bind the event to the signature of its choosing. I wanted the subject class inheriting from Observable to be able to configure the association of event to function signature, and force observers to abide to that, or have compile error if they use it wrong.
            – bone
            Jan 16 at 22:01










          • The observable class is doing the configuring via the attachmethod. The observers are regular lambdas or functions, nothing fancy which could bind events and signatures. As to compile time errors, you're right, it's a good thing to strive for. It's a question of balance, because on the other hand your interface is quite restrictive (no run-time attaching lacks flexibility) and a bit ugly. I guess you could try and combine both approach in some way .
            – papagaga
            Jan 16 at 23:02
















          • Damn! I'll need some time to read this thoroughly before I can understand what it's doing. Thanks for taking the time to respond!
            – bone
            Jan 16 at 14:20










          • Edit: So I tried your code, but I changed the last attach to: sun.attach(MACHINES, [&coffee_machine](int h, int i) coffee_machine.make_coffee(h+i);); And it fails at runtime with a bad_any_cast exception. My idea was to have the events be tied to a specific function signature at compile time. Your idea is good, but it is not exactly what I am intending.
            – bone
            Jan 16 at 15:35










          • Sure it fails at runtime. Machines is the equivalent of one of your events and is tied to a signature with only one int. There's a mapping from a run time integer (an enum in my code to be more expressive) to a function signature.
            – papagaga
            Jan 16 at 20:36










          • Yes I see. IF I understand it correctly, the problem with your code is that the first observer calling attach will bind the event to the signature of its choosing. I wanted the subject class inheriting from Observable to be able to configure the association of event to function signature, and force observers to abide to that, or have compile error if they use it wrong.
            – bone
            Jan 16 at 22:01










          • The observable class is doing the configuring via the attachmethod. The observers are regular lambdas or functions, nothing fancy which could bind events and signatures. As to compile time errors, you're right, it's a good thing to strive for. It's a question of balance, because on the other hand your interface is quite restrictive (no run-time attaching lacks flexibility) and a bit ugly. I guess you could try and combine both approach in some way .
            – papagaga
            Jan 16 at 23:02















          Damn! I'll need some time to read this thoroughly before I can understand what it's doing. Thanks for taking the time to respond!
          – bone
          Jan 16 at 14:20




          Damn! I'll need some time to read this thoroughly before I can understand what it's doing. Thanks for taking the time to respond!
          – bone
          Jan 16 at 14:20












          Edit: So I tried your code, but I changed the last attach to: sun.attach(MACHINES, [&coffee_machine](int h, int i) coffee_machine.make_coffee(h+i);); And it fails at runtime with a bad_any_cast exception. My idea was to have the events be tied to a specific function signature at compile time. Your idea is good, but it is not exactly what I am intending.
          – bone
          Jan 16 at 15:35




          Edit: So I tried your code, but I changed the last attach to: sun.attach(MACHINES, [&coffee_machine](int h, int i) coffee_machine.make_coffee(h+i);); And it fails at runtime with a bad_any_cast exception. My idea was to have the events be tied to a specific function signature at compile time. Your idea is good, but it is not exactly what I am intending.
          – bone
          Jan 16 at 15:35












          Sure it fails at runtime. Machines is the equivalent of one of your events and is tied to a signature with only one int. There's a mapping from a run time integer (an enum in my code to be more expressive) to a function signature.
          – papagaga
          Jan 16 at 20:36




          Sure it fails at runtime. Machines is the equivalent of one of your events and is tied to a signature with only one int. There's a mapping from a run time integer (an enum in my code to be more expressive) to a function signature.
          – papagaga
          Jan 16 at 20:36












          Yes I see. IF I understand it correctly, the problem with your code is that the first observer calling attach will bind the event to the signature of its choosing. I wanted the subject class inheriting from Observable to be able to configure the association of event to function signature, and force observers to abide to that, or have compile error if they use it wrong.
          – bone
          Jan 16 at 22:01




          Yes I see. IF I understand it correctly, the problem with your code is that the first observer calling attach will bind the event to the signature of its choosing. I wanted the subject class inheriting from Observable to be able to configure the association of event to function signature, and force observers to abide to that, or have compile error if they use it wrong.
          – bone
          Jan 16 at 22:01












          The observable class is doing the configuring via the attachmethod. The observers are regular lambdas or functions, nothing fancy which could bind events and signatures. As to compile time errors, you're right, it's a good thing to strive for. It's a question of balance, because on the other hand your interface is quite restrictive (no run-time attaching lacks flexibility) and a bit ugly. I guess you could try and combine both approach in some way .
          – papagaga
          Jan 16 at 23:02




          The observable class is doing the configuring via the attachmethod. The observers are regular lambdas or functions, nothing fancy which could bind events and signatures. As to compile time errors, you're right, it's a good thing to strive for. It's a question of balance, because on the other hand your interface is quite restrictive (no run-time attaching lacks flexibility) and a bit ugly. I guess you could try and combine both approach in some way .
          – papagaga
          Jan 16 at 23:02












           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f185114%2fimplementation-of-observable-with-multiple-callback-function-signatures%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Chat program with C++ and SFML

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          Will my employers contract hold up in court?