Implementation of Observable with multiple callback function signatures
Clash 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.
c++ c++14 observer-pattern
add a comment |Â
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.
c++ c++14 observer-pattern
add a comment |Â
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.
c++ c++14 observer-pattern
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.
c++ c++14 observer-pattern
asked Jan 14 at 22:31
bone
1264
1264
add a comment |Â
add a comment |Â
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();
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
Theobservable
class is doing the configuring via theattach
method. 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-timeattach
ing lacks flexibility) and a bit ugly. I guess you could try and combine both approach in some way .
â papagaga
Jan 16 at 23:02
add a comment |Â
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();
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
Theobservable
class is doing the configuring via theattach
method. 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-timeattach
ing lacks flexibility) and a bit ugly. I guess you could try and combine both approach in some way .
â papagaga
Jan 16 at 23:02
add a comment |Â
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();
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
Theobservable
class is doing the configuring via theattach
method. 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-timeattach
ing lacks flexibility) and a bit ugly. I guess you could try and combine both approach in some way .
â papagaga
Jan 16 at 23:02
add a comment |Â
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();
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();
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
Theobservable
class is doing the configuring via theattach
method. 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-timeattach
ing lacks flexibility) and a bit ugly. I guess you could try and combine both approach in some way .
â papagaga
Jan 16 at 23:02
add a comment |Â
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
Theobservable
class is doing the configuring via theattach
method. 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-timeattach
ing 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 attach
method. 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 attach
ing 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 attach
method. 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 attach
ing lacks flexibility) and a bit ugly. I guess you could try and combine both approach in some way .â papagaga
Jan 16 at 23:02
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password