#include <iostream>
#include <vector>
#include <map>
#include <tuple>
enum OutputType { Int, Bool, String, NumOutputTypes, FirstOutputType = Int };
template <typename T> struct GetOutputType;
template <> struct GetOutputType<int> : std::integral_constant<OutputType, Int> { };
template <> struct GetOutputType<bool> : std::integral_constant<OutputType, Bool> { };
template <> struct GetOutputType<std::string> : std::integral_constant<OutputType, String> { };
template <typename T>
struct Observer {
virtual void field_changed (T&, const std::string& field_name, OutputType) = 0;
};
template <typename T>
class Observable {
std::vector<Observer<T>*> observers;
public:
void notify_field_change (T& source, OutputType output_type, const std::string& field_name) {
for (Observer<T>* obs : observers)
obs->field_changed(source, field_name, output_type);
}
void subscribe (Observer<T>* f) { observers.push_back(f); }
void unsubscribe (Observer<T>* f) { observers.erase(remove(observers.begin(), observers.end(), f), observers.end()); }
};
class Person : public Observable<Person> {
int age, height;
std::string name, country, zip_code;
static const std::map<std::string, std::vector<std::pair<std::string, OutputType>>> dependenciesMap; // Keeps a list of properties affected by a property.
public:
Person (const std::string& n, int a, const std::string& c, const std::string& z) : name(n), age(a), country(c), zip_code(z) { }
static const std::map<std::string, int(Person::*)() const> intMethodMap;
static const std::map<std::string, bool(Person::*)() const> boolMethodMap;
static const std::map<std::string, std::string(Person::*)() const> stringMethodMap;
using TupleMaps = std::tuple<decltype(intMethodMap), decltype(boolMethodMap), decltype(stringMethodMap)>;
using TupleMaps2 = std::tuple<std::map<std::string, int>, std::map<std::string, bool>, std::map<std::string, std::string>>;
static const TupleMaps methodMaps;
int get_age() const { return age; }
void set_age (int a) { set_field(&Person::age, "age", a); }
int get_height() const { return height; }
std::string get_name() const { return name; }
std::string nickname() const {
if (country != "France")
return name;
return "Francois";
}
std::string get_country() const { return country; }
void set_country (const std::string& c) { set_field(&Person::country, "country", c); }
std::string get_zip_code() const { return zip_code; }
void set_zip_code (const std::string& z) { set_field(&Person::zip_code, "zip_code", z); }
bool can_vote() const { return age >= 16 && country == "USA"; }
bool can_quit_school() const { return age >= 15 && country == "USA" && zip_code == "90210"; }
int years_left() const {
if (country == "France")
return 10;
return 80 - age;
}
private:
template <typename T> void set_field (T Person::*, const std::string& field_name, const T& value); // T Person::* is pointer to data member.
template <OutputType> void set_field_helper (TupleMaps2& trackedValuesAllTypes, const std::pair<std::string, OutputType>&);
template <OutputType> void notify_other_field_changes (const TupleMaps2&);
};
const std::map<std::string, std::vector<std::pair<std::string, OutputType>>> Person::dependenciesMap = {
{"age", {{"can_vote()", Bool}, {"can_quit_school()", Bool}, {"years_left()", Int}}}, // age affects can_vote(), can_quit_school(), and years_left().
{"country", {{"can_vote()", Bool}, {"can_quit_school()", Bool}, {"years_left()", Int}, {"nickname", String}}}, // country affects can_vote, can_quit_school(), years_left(), and nickname.
{"zip_code", {{"can_quit_school()", Bool}}} // zip_code affects can_quit_school
};
const std::map<std::string, int(Person::*)() const> Person::intMethodMap = {
{"age", &Person::get_age}, {"height", &Person::get_height}, {"years_left()", &Person::years_left}
};
const std::map<std::string, bool(Person::*)() const> Person::boolMethodMap = {
{"can_vote()", &Person::can_vote}, {"can_quit_school()", &Person::can_quit_school}
};
const std::map<std::string, std::string(Person::*)() const> Person::stringMethodMap = {
{"name", &Person::get_name}, {"country", &Person::get_country}, {"zip_code", &Person::get_zip_code}, {"nickname", &Person::nickname}
};
const Person::TupleMaps Person::methodMaps = std::make_tuple(Person::intMethodMap, Person::boolMethodMap, Person::stringMethodMap);
struct ConsolePersonObserver : Observer<Person> {
virtual void field_changed (Person& source, const std::string& field_name, OutputType output_type) override { field_changed_helper<FirstOutputType>(source, field_name, output_type); }
private:
template <OutputType> void field_changed_helper (Person& source, const std::string& field_name, OutputType output_type);
};
template <OutputType N>
void ConsolePersonObserver::field_changed_helper (Person& source, const std::string& field_name, OutputType output_type) {
if (output_type == N)
std::cout << source.get_name() << "'s " << field_name << " has changed to " << (source.*std::get<N>(Person::methodMaps).at(field_name))() << ".\n";
else
field_changed_helper<static_cast<OutputType>(N+1)>(source, field_name, output_type);
}
template <>
void ConsolePersonObserver::field_changed_helper<NumOutputTypes> (Person&, const std::string&, OutputType) { } // End of recursion
template <typename T>
void Person::set_field (T Person::*ptr, const std::string& field_name, const T& value) {
if (value == this->*ptr)
return;
std::tuple<std::map<std::string, int>, std::map<std::string, bool>, std::map<std::string, std::string>>TupleMaps2 trackedValuesAllTypes;
for (const std::pair<std::string, OutputType>& pair : dependenciesMap.at(field_name))
set_field_helper<FirstOutputType>(trackedValuesAllTypes, pair);
this->*ptr = value;
const OutputType type = GetOutputType<typename std::remove_reference<decltype(this->*ptr)>::type>::value;
notify_field_change(*this, type, field_name);
notify_other_field_changes<FirstOutputType>(trackedValuesAllTypes);
}
template <OutputType N>
void Person::set_field_helper (TupleMaps2& trackedValuesAllTypes, const std::pair<std::string, OutputType>& pair) {
if (pair.second == N)
std::get<N>(trackedValuesAllTypes)[pair.first] = (this->*std::get<N>(Person::methodMaps).at(pair.first))();
else
set_field_helper<static_cast<OutputType>(N+1)>(trackedValuesAllTypes, pair);
}
template <>
void Person::set_field_helper<NumOutputTypes> (TupleMaps2&, const std::pair<std::string, OutputType>&) { } // End of recursion.
template <OutputType N>
void Person::notify_other_field_changes (const TupleMaps2& trackedValuesAllTypes) {
for (const auto& pair : std::get<N>(trackedValuesAllTypes)) {
if (pair.second != (this->*std::get<N>(methodMaps).at(pair.first))())
notify_field_change(*this, N, pair.first);
}
notify_other_field_changes<static_cast<OutputType>(N+1)>(trackedValuesAllTypes);
}
template <>
void Person::notify_other_field_changes<NumOutputTypes> (const TupleMaps2&) { } // End of recursion.
int main() {
Person p("Tom", 14, "USA", "90210");
ConsolePersonObserver cpo;
p.subscribe(&cpo);
p.set_age(15);
p.set_age(16);
p.set_zip_code("999999");
p.set_country("France");
}
#include <iostream>
#include <vector>
#include <map>
#include <tuple>
enum OutputType { Int, Bool, String, NumOutputTypes, FirstOutputType = Int };
template <typename T> struct GetOutputType;
template <> struct GetOutputType<int> : std::integral_constant<OutputType, Int> { };
template <> struct GetOutputType<bool> : std::integral_constant<OutputType, Bool> { };
template <> struct GetOutputType<std::string> : std::integral_constant<OutputType, String> { };
template <typename T>
struct Observer {
virtual void field_changed (T&, const std::string& field_name, OutputType) = 0;
};
template <typename T>
class Observable {
std::vector<Observer<T>*> observers;
public:
void notify_field_change (T& source, OutputType output_type, const std::string& field_name) {
for (Observer<T>* obs : observers)
obs->field_changed(source, field_name, output_type);
}
void subscribe (Observer<T>* f) { observers.push_back(f); }
void unsubscribe (Observer<T>* f) { observers.erase(remove(observers.begin(), observers.end(), f), observers.end()); }
};
class Person : public Observable<Person> {
int age, height;
std::string name, country, zip_code;
static const std::map<std::string, std::vector<std::pair<std::string, OutputType>>> dependenciesMap; // Keeps a list of properties affected by a property.
public:
Person (const std::string& n, int a, const std::string& c, const std::string& z) : name(n), age(a), country(c), zip_code(z) { }
static const std::map<std::string, int(Person::*)() const> intMethodMap;
static const std::map<std::string, bool(Person::*)() const> boolMethodMap;
static const std::map<std::string, std::string(Person::*)() const> stringMethodMap;
using TupleMaps = std::tuple<decltype(intMethodMap), decltype(boolMethodMap), decltype(stringMethodMap)>;
using TupleMaps2 = std::tuple<std::map<std::string, int>, std::map<std::string, bool>, std::map<std::string, std::string>>;
static const TupleMaps methodMaps;
int get_age() const { return age; }
void set_age (int a) { set_field(&Person::age, "age", a); }
int get_height() const { return height; }
std::string get_name() const { return name; }
std::string nickname() const {
if (country != "France")
return name;
return "Francois";
}
std::string get_country() const { return country; }
void set_country (const std::string& c) { set_field(&Person::country, "country", c); }
std::string get_zip_code() const { return zip_code; }
void set_zip_code (const std::string& z) { set_field(&Person::zip_code, "zip_code", z); }
bool can_vote() const { return age >= 16 && country == "USA"; }
bool can_quit_school() const { return age >= 15 && country == "USA" && zip_code == "90210"; }
int years_left() const {
if (country == "France")
return 10;
return 80 - age;
}
private:
template <typename T> void set_field (T Person::*, const std::string& field_name, const T& value); // T Person::* is pointer to data member.
template <OutputType> void set_field_helper (TupleMaps2& trackedValuesAllTypes, const std::pair<std::string, OutputType>&);
template <OutputType> void notify_other_field_changes (const TupleMaps2&);
};
const std::map<std::string, std::vector<std::pair<std::string, OutputType>>> Person::dependenciesMap = {
{"age", {{"can_vote()", Bool}, {"can_quit_school()", Bool}, {"years_left()", Int}}}, // age affects can_vote(), can_quit_school(), and years_left().
{"country", {{"can_vote()", Bool}, {"can_quit_school()", Bool}, {"years_left()", Int}, {"nickname", String}}}, // country affects can_vote, can_quit_school(), years_left(), and nickname.
{"zip_code", {{"can_quit_school()", Bool}}} // zip_code affects can_quit_school
};
const std::map<std::string, int(Person::*)() const> Person::intMethodMap = {
{"age", &Person::get_age}, {"height", &Person::get_height}, {"years_left()", &Person::years_left}
};
const std::map<std::string, bool(Person::*)() const> Person::boolMethodMap = {
{"can_vote()", &Person::can_vote}, {"can_quit_school()", &Person::can_quit_school}
};
const std::map<std::string, std::string(Person::*)() const> Person::stringMethodMap = {
{"name", &Person::get_name}, {"country", &Person::get_country}, {"zip_code", &Person::get_zip_code}, {"nickname", &Person::nickname}
};
const Person::TupleMaps Person::methodMaps = std::make_tuple(Person::intMethodMap, Person::boolMethodMap, Person::stringMethodMap);
struct ConsolePersonObserver : Observer<Person> {
virtual void field_changed (Person& source, const std::string& field_name, OutputType output_type) override { field_changed_helper<FirstOutputType>(source, field_name, output_type); }
private:
template <OutputType> void field_changed_helper (Person& source, const std::string& field_name, OutputType output_type);
};
template <OutputType N>
void ConsolePersonObserver::field_changed_helper (Person& source, const std::string& field_name, OutputType output_type) {
if (output_type == N)
std::cout << source.get_name() << "'s " << field_name << " has changed to " << (source.*std::get<N>(Person::methodMaps).at(field_name))() << ".\n";
else
field_changed_helper<static_cast<OutputType>(N+1)>(source, field_name, output_type);
}
template <>
void ConsolePersonObserver::field_changed_helper<NumOutputTypes> (Person&, const std::string&, OutputType) { } // End of recursion
template <typename T>
void Person::set_field (T Person::*ptr, const std::string& field_name, const T& value) {
if (value == this->*ptr)
return;
std::tuple<std::map<std::string, int>, std::map<std::string, bool>, std::map<std::string, std::string>> trackedValuesAllTypes;
for (const std::pair<std::string, OutputType>& pair : dependenciesMap.at(field_name))
set_field_helper<FirstOutputType>(trackedValuesAllTypes, pair);
this->*ptr = value;
const OutputType type = GetOutputType<typename std::remove_reference<decltype(this->*ptr)>::type>::value;
notify_field_change(*this, type, field_name);
notify_other_field_changes<FirstOutputType>(trackedValuesAllTypes);
}
template <OutputType N>
void Person::set_field_helper (TupleMaps2& trackedValuesAllTypes, const std::pair<std::string, OutputType>& pair) {
if (pair.second == N)
std::get<N>(trackedValuesAllTypes)[pair.first] = (this->*std::get<N>(Person::methodMaps).at(pair.first))();
else
set_field_helper<static_cast<OutputType>(N+1)>(trackedValuesAllTypes, pair);
}
template <>
void Person::set_field_helper<NumOutputTypes> (TupleMaps2&, const std::pair<std::string, OutputType>&) { } // End of recursion.
template <OutputType N>
void Person::notify_other_field_changes (const TupleMaps2& trackedValuesAllTypes) {
for (const auto& pair : std::get<N>(trackedValuesAllTypes)) {
if (pair.second != (this->*std::get<N>(methodMaps).at(pair.first))())
notify_field_change(*this, N, pair.first);
}
notify_other_field_changes<static_cast<OutputType>(N+1)>(trackedValuesAllTypes);
}
template <>
void Person::notify_other_field_changes<NumOutputTypes> (const TupleMaps2&) { } // End of recursion.
int main() {
Person p("Tom", 14, "USA", "90210");
ConsolePersonObserver cpo;
p.subscribe(&cpo);
p.set_age(15);
p.set_age(16);
p.set_zip_code("999999");
p.set_country("France");
}
#include <iostream>
#include <vector>
#include <map>
#include <tuple>
enum OutputType { Int, Bool, String, NumOutputTypes, FirstOutputType = Int };
template <typename T> struct GetOutputType;
template <> struct GetOutputType<int> : std::integral_constant<OutputType, Int> { };
template <> struct GetOutputType<bool> : std::integral_constant<OutputType, Bool> { };
template <> struct GetOutputType<std::string> : std::integral_constant<OutputType, String> { };
template <typename T>
struct Observer {
virtual void field_changed (T&, const std::string& field_name, OutputType) = 0;
};
template <typename T>
class Observable {
std::vector<Observer<T>*> observers;
public:
void notify_field_change (T& source, OutputType output_type, const std::string& field_name) {
for (Observer<T>* obs : observers)
obs->field_changed(source, field_name, output_type);
}
void subscribe (Observer<T>* f) { observers.push_back(f); }
void unsubscribe (Observer<T>* f) { observers.erase(remove(observers.begin(), observers.end(), f), observers.end()); }
};
class Person : public Observable<Person> {
int age, height;
std::string name, country, zip_code;
static const std::map<std::string, std::vector<std::pair<std::string, OutputType>>> dependenciesMap; // Keeps a list of properties affected by a property.
public:
Person (const std::string& n, int a, const std::string& c, const std::string& z) : name(n), age(a), country(c), zip_code(z) { }
static const std::map<std::string, int(Person::*)() const> intMethodMap;
static const std::map<std::string, bool(Person::*)() const> boolMethodMap;
static const std::map<std::string, std::string(Person::*)() const> stringMethodMap;
using TupleMaps = std::tuple<decltype(intMethodMap), decltype(boolMethodMap), decltype(stringMethodMap)>;
using TupleMaps2 = std::tuple<std::map<std::string, int>, std::map<std::string, bool>, std::map<std::string, std::string>>;
static const TupleMaps methodMaps;
int get_age() const { return age; }
void set_age (int a) { set_field(&Person::age, "age", a); }
int get_height() const { return height; }
std::string get_name() const { return name; }
std::string nickname() const {
if (country != "France")
return name;
return "Francois";
}
std::string get_country() const { return country; }
void set_country (const std::string& c) { set_field(&Person::country, "country", c); }
std::string get_zip_code() const { return zip_code; }
void set_zip_code (const std::string& z) { set_field(&Person::zip_code, "zip_code", z); }
bool can_vote() const { return age >= 16 && country == "USA"; }
bool can_quit_school() const { return age >= 15 && country == "USA" && zip_code == "90210"; }
int years_left() const {
if (country == "France")
return 10;
return 80 - age;
}
private:
template <typename T> void set_field (T Person::*, const std::string& field_name, const T& value); // T Person::* is pointer to data member.
template <OutputType> void set_field_helper (TupleMaps2& trackedValuesAllTypes, const std::pair<std::string, OutputType>&);
template <OutputType> void notify_other_field_changes (const TupleMaps2&);
};
const std::map<std::string, std::vector<std::pair<std::string, OutputType>>> Person::dependenciesMap = {
{"age", {{"can_vote()", Bool}, {"can_quit_school()", Bool}, {"years_left()", Int}}}, // age affects can_vote(), can_quit_school(), and years_left().
{"country", {{"can_vote()", Bool}, {"can_quit_school()", Bool}, {"years_left()", Int}, {"nickname", String}}}, // country affects can_vote, can_quit_school(), years_left(), and nickname.
{"zip_code", {{"can_quit_school()", Bool}}} // zip_code affects can_quit_school
};
const std::map<std::string, int(Person::*)() const> Person::intMethodMap = {
{"age", &Person::get_age}, {"height", &Person::get_height}, {"years_left()", &Person::years_left}
};
const std::map<std::string, bool(Person::*)() const> Person::boolMethodMap = {
{"can_vote()", &Person::can_vote}, {"can_quit_school()", &Person::can_quit_school}
};
const std::map<std::string, std::string(Person::*)() const> Person::stringMethodMap = {
{"name", &Person::get_name}, {"country", &Person::get_country}, {"zip_code", &Person::get_zip_code}, {"nickname", &Person::nickname}
};
const Person::TupleMaps Person::methodMaps = std::make_tuple(Person::intMethodMap, Person::boolMethodMap, Person::stringMethodMap);
struct ConsolePersonObserver : Observer<Person> {
virtual void field_changed (Person& source, const std::string& field_name, OutputType output_type) override { field_changed_helper<FirstOutputType>(source, field_name, output_type); }
private:
template <OutputType> void field_changed_helper (Person& source, const std::string& field_name, OutputType output_type);
};
template <OutputType N>
void ConsolePersonObserver::field_changed_helper (Person& source, const std::string& field_name, OutputType output_type) {
if (output_type == N)
std::cout << source.get_name() << "'s " << field_name << " has changed to " << (source.*std::get<N>(Person::methodMaps).at(field_name))() << ".\n";
else
field_changed_helper<static_cast<OutputType>(N+1)>(source, field_name, output_type);
}
template <>
void ConsolePersonObserver::field_changed_helper<NumOutputTypes> (Person&, const std::string&, OutputType) { } // End of recursion
template <typename T>
void Person::set_field (T Person::*ptr, const std::string& field_name, const T& value) {
if (value == this->*ptr)
return;
TupleMaps2 trackedValuesAllTypes;
for (const std::pair<std::string, OutputType>& pair : dependenciesMap.at(field_name))
set_field_helper<FirstOutputType>(trackedValuesAllTypes, pair);
this->*ptr = value;
const OutputType type = GetOutputType<typename std::remove_reference<decltype(this->*ptr)>::type>::value;
notify_field_change(*this, type, field_name);
notify_other_field_changes<FirstOutputType>(trackedValuesAllTypes);
}
template <OutputType N>
void Person::set_field_helper (TupleMaps2& trackedValuesAllTypes, const std::pair<std::string, OutputType>& pair) {
if (pair.second == N)
std::get<N>(trackedValuesAllTypes)[pair.first] = (this->*std::get<N>(Person::methodMaps).at(pair.first))();
else
set_field_helper<static_cast<OutputType>(N+1)>(trackedValuesAllTypes, pair);
}
template <>
void Person::set_field_helper<NumOutputTypes> (TupleMaps2&, const std::pair<std::string, OutputType>&) { } // End of recursion.
template <OutputType N>
void Person::notify_other_field_changes (const TupleMaps2& trackedValuesAllTypes) {
for (const auto& pair : std::get<N>(trackedValuesAllTypes)) {
if (pair.second != (this->*std::get<N>(methodMaps).at(pair.first))())
notify_field_change(*this, N, pair.first);
}
notify_other_field_changes<static_cast<OutputType>(N+1)>(trackedValuesAllTypes);
}
template <>
void Person::notify_other_field_changes<NumOutputTypes> (const TupleMaps2&) { } // End of recursion.
int main() {
Person p("Tom", 14, "USA", "90210");
ConsolePersonObserver cpo;
p.subscribe(&cpo);
p.set_age(15);
p.set_age(16);
p.set_zip_code("999999");
p.set_country("France");
}
Here is my extended code to handle the general case when the output types from the methods obtained by dependenciesMap
are different (I removed the template argument of notify_field_changednotify_field_change
to make this work). For example, country
affects can_vote
, can_quit_school()
, years_left()
, and nickname
, which return bool, bool, int, and std::string respectively. But I have a feeling there is a cleaner way to handle this than my code below:
Here is my extended code to handle the general case when the output types from the methods obtained by dependenciesMap
are different (I removed the template argument of notify_field_changed
to make this work):
Here is my extended code to handle the general case when the output types from the methods obtained by dependenciesMap
are different (I removed the template argument of notify_field_change
to make this work). For example, country
affects can_vote
, can_quit_school()
, years_left()
, and nickname
, which return bool, bool, int, and std::string respectively. But I have a feeling there is a cleaner way to handle this than my code below:
Here is my extended code to handle the general case when the output types from the methods obtained by dependenciesMap
are different (I removed the template argument of notify_field_changed
to make this work):
#include <iostream>
#include <vector>
#include <map>
#include <tuple>
enum OutputType { Int, Bool, String, NumOutputTypes, FirstOutputType = Int };
template <typename T> struct GetOutputType;
template <> struct GetOutputType<int> : std::integral_constant<OutputType, Int> { };
template <> struct GetOutputType<bool> : std::integral_constant<OutputType, Bool> { };
template <> struct GetOutputType<std::string> : std::integral_constant<OutputType, String> { };
template <typename T>
struct Observer {
virtual void field_changed (T&, const std::string& field_name, OutputType) = 0;
};
template <typename T>
class Observable {
std::vector<Observer<T>*> observers;
public:
void notify_field_change (T& source, OutputType output_type, const std::string& field_name) {
for (Observer<T>* obs : observers)
obs->field_changed(source, field_name, output_type);
}
void subscribe (Observer<T>* f) { observers.push_back(f); }
void unsubscribe (Observer<T>* f) { observers.erase(remove(observers.begin(), observers.end(), f), observers.end()); }
};
class Person : public Observable<Person> {
int age, height;
std::string name, country, zip_code;
static const std::map<std::string, std::vector<std::pair<std::string, OutputType>>> dependenciesMap; // Keeps a list of properties affected by a property.
public:
Person (const std::string& n, int a, const std::string& c, const std::string& z) : name(n), age(a), country(c), zip_code(z) { }
static const std::map<std::string, int(Person::*)() const> intMethodMap;
static const std::map<std::string, bool(Person::*)() const> boolMethodMap;
static const std::map<std::string, std::string(Person::*)() const> stringMethodMap;
using TupleMaps = std::tuple<decltype(intMethodMap), decltype(boolMethodMap), decltype(stringMethodMap)>;
using TupleMaps2 = std::tuple<std::map<std::string, int>, std::map<std::string, bool>, std::map<std::string, std::string>>;
static const TupleMaps methodMaps;
int get_age() const { return age; }
void set_age (int a) { set_field(&Person::age, "age", a); }
int get_height() const { return height; }
std::string get_name() const { return name; }
std::string nickname() const {
if (country != "France")
return name;
return "Francois";
}
std::string get_country() const { return country; }
void set_country (const std::string& c) { set_field(&Person::country, "country", c); }
std::string get_zip_code() const { return zip_code; }
void set_zip_code (const std::string& z) { set_field(&Person::zip_code, "zip_code", z); }
bool can_vote() const { return age >= 16 && country == "USA"; }
bool can_quit_school() const { return age >= 15 && country == "USA" && zip_code == "90210"; }
int years_left() const {
if (country == "France")
return 10;
return 80 - age;
}
private:
template <typename T> void set_field (T Person::*, const std::string& field_name, const T& value); // T Person::* is pointer to data member.
template <OutputType> void set_field_helper (TupleMaps2& trackedValuesAllTypes, const std::pair<std::string, OutputType>&);
template <OutputType> void notify_other_field_changes (const TupleMaps2&);
};
const std::map<std::string, std::vector<std::pair<std::string, OutputType>>> Person::dependenciesMap = {
{"age", {{"can_vote()", Bool}, {"can_quit_school()", Bool}, {"years_left()", Int}}}, // age affects can_vote(), can_quit_school(), and years_left().
{"country", {{"can_vote()", Bool}, {"can_quit_school()", Bool}, {"years_left()", Int}, {"nickname", String}}}, // country affects can_vote, can_quit_school(), years_left(), and nickname.
{"zip_code", {{"can_quit_school()", Bool}}} // zip_code affects can_quit_school
};
const std::map<std::string, int(Person::*)() const> Person::intMethodMap = {
{"age", &Person::get_age}, {"height", &Person::get_height}, {"years_left()", &Person::years_left}
};
const std::map<std::string, bool(Person::*)() const> Person::boolMethodMap = {
{"can_vote()", &Person::can_vote}, {"can_quit_school()", &Person::can_quit_school}
};
const std::map<std::string, std::string(Person::*)() const> Person::stringMethodMap = {
{"name", &Person::get_name}, {"country", &Person::get_country}, {"zip_code", &Person::get_zip_code}, {"nickname", &Person::nickname}
};
const Person::TupleMaps Person::methodMaps = std::make_tuple(Person::intMethodMap, Person::boolMethodMap, Person::stringMethodMap);
struct ConsolePersonObserver : Observer<Person> {
virtual void field_changed (Person& source, const std::string& field_name, OutputType output_type) override { field_changed_helper<FirstOutputType>(source, field_name, output_type); }
private:
template <OutputType> void field_changed_helper (Person& source, const std::string& field_name, OutputType output_type);
};
template <OutputType N>
void ConsolePersonObserver::field_changed_helper (Person& source, const std::string& field_name, OutputType output_type) {
if (output_type == N)
std::cout << source.get_name() << "'s " << field_name << " has changed to " << (source.*std::get<N>(Person::methodMaps).at(field_name))() << ".\n";
else
field_changed_helper<static_cast<OutputType>(N+1)>(source, field_name, output_type);
}
template <>
void ConsolePersonObserver::field_changed_helper<NumOutputTypes> (Person&, const std::string&, OutputType) { } // End of recursion
template <typename T>
void Person::set_field (T Person::*ptr, const std::string& field_name, const T& value) {
if (value == this->*ptr)
return;
std::tuple<std::map<std::string, int>, std::map<std::string, bool>, std::map<std::string, std::string>> trackedValuesAllTypes;
for (const std::pair<std::string, OutputType>& pair : dependenciesMap.at(field_name))
set_field_helper<FirstOutputType>(trackedValuesAllTypes, pair);
this->*ptr = value;
const OutputType type = GetOutputType<typename std::remove_reference<decltype(this->*ptr)>::type>::value;
notify_field_change(*this, type, field_name);
notify_other_field_changes<FirstOutputType>(trackedValuesAllTypes);
}
template <OutputType N>
void Person::set_field_helper (TupleMaps2& trackedValuesAllTypes, const std::pair<std::string, OutputType>& pair) {
if (pair.second == N)
std::get<N>(trackedValuesAllTypes)[pair.first] = (this->*std::get<N>(Person::methodMaps).at(pair.first))();
else
set_field_helper<static_cast<OutputType>(N+1)>(trackedValuesAllTypes, pair);
}
template <>
void Person::set_field_helper<NumOutputTypes> (TupleMaps2&, const std::pair<std::string, OutputType>&) { } // End of recursion.
template <OutputType N>
void Person::notify_other_field_changes (const TupleMaps2& trackedValuesAllTypes) {
for (const auto& pair : std::get<N>(trackedValuesAllTypes)) {
if (pair.second != (this->*std::get<N>(methodMaps).at(pair.first))())
notify_field_change(*this, N, pair.first);
}
notify_other_field_changes<static_cast<OutputType>(N+1)>(trackedValuesAllTypes);
}
template <>
void Person::notify_other_field_changes<NumOutputTypes> (const TupleMaps2&) { } // End of recursion.
int main() {
Person p("Tom", 14, "USA", "90210");
ConsolePersonObserver cpo;
p.subscribe(&cpo);
p.set_age(15);
p.set_age(16);
p.set_zip_code("999999");
p.set_country("France");
}
Output:
Tom's age has changed to 15.
Tom's years_left() has changed to 65.
Tom's can_quit_school() has changed to 1.
Tom's age has changed to 16.
Tom's years_left() has changed to 64.
Tom's can_vote() has changed to 1.
Tom's zip_code has changed to 999999.
Tom's can_quit_school() has changed to 0.
Tom's country has changed to France.
Tom's years_left() has changed to 10.
Tom's can_vote() has changed to 0.
Tom's nickname has changed to Francois.
Here is my extended code to handle the general case when the output types from the methods obtained by dependenciesMap
are different (I removed the template argument of notify_field_changed
to make this work):
#include <iostream>
#include <vector>
#include <map>
#include <tuple>
enum OutputType { Int, Bool, String, NumOutputTypes, FirstOutputType = Int };
template <typename T> struct GetOutputType;
template <> struct GetOutputType<int> : std::integral_constant<OutputType, Int> { };
template <> struct GetOutputType<bool> : std::integral_constant<OutputType, Bool> { };
template <> struct GetOutputType<std::string> : std::integral_constant<OutputType, String> { };
template <typename T>
struct Observer {
virtual void field_changed (T&, const std::string& field_name, OutputType) = 0;
};
template <typename T>
class Observable {
std::vector<Observer<T>*> observers;
public:
void notify_field_change (T& source, OutputType output_type, const std::string& field_name) {
for (Observer<T>* obs : observers)
obs->field_changed(source, field_name, output_type);
}
void subscribe (Observer<T>* f) { observers.push_back(f); }
void unsubscribe (Observer<T>* f) { observers.erase(remove(observers.begin(), observers.end(), f), observers.end()); }
};
class Person : public Observable<Person> {
int age, height;
std::string name, country, zip_code;
static const std::map<std::string, std::vector<std::pair<std::string, OutputType>>> dependenciesMap; // Keeps a list of properties affected by a property.
public:
Person (const std::string& n, int a, const std::string& c, const std::string& z) : name(n), age(a), country(c), zip_code(z) { }
static const std::map<std::string, int(Person::*)() const> intMethodMap;
static const std::map<std::string, bool(Person::*)() const> boolMethodMap;
static const std::map<std::string, std::string(Person::*)() const> stringMethodMap;
using TupleMaps = std::tuple<decltype(intMethodMap), decltype(boolMethodMap), decltype(stringMethodMap)>;
using TupleMaps2 = std::tuple<std::map<std::string, int>, std::map<std::string, bool>, std::map<std::string, std::string>>;
static const TupleMaps methodMaps;
int get_age() const { return age; }
void set_age (int a) { set_field(&Person::age, "age", a); }
int get_height() const { return height; }
std::string get_name() const { return name; }
std::string nickname() const {
if (country != "France")
return name;
return "Francois";
}
std::string get_country() const { return country; }
void set_country (const std::string& c) { set_field(&Person::country, "country", c); }
std::string get_zip_code() const { return zip_code; }
void set_zip_code (const std::string& z) { set_field(&Person::zip_code, "zip_code", z); }
bool can_vote() const { return age >= 16 && country == "USA"; }
bool can_quit_school() const { return age >= 15 && country == "USA" && zip_code == "90210"; }
int years_left() const {
if (country == "France")
return 10;
return 80 - age;
}
private:
template <typename T> void set_field (T Person::*, const std::string& field_name, const T& value); // T Person::* is pointer to data member.
template <OutputType> void set_field_helper (TupleMaps2& trackedValuesAllTypes, const std::pair<std::string, OutputType>&);
template <OutputType> void notify_other_field_changes (const TupleMaps2&);
};
const std::map<std::string, std::vector<std::pair<std::string, OutputType>>> Person::dependenciesMap = {
{"age", {{"can_vote()", Bool}, {"can_quit_school()", Bool}, {"years_left()", Int}}}, // age affects can_vote(), can_quit_school(), and years_left().
{"country", {{"can_vote()", Bool}, {"can_quit_school()", Bool}, {"years_left()", Int}, {"nickname", String}}}, // country affects can_vote, can_quit_school(), years_left(), and nickname.
{"zip_code", {{"can_quit_school()", Bool}}} // zip_code affects can_quit_school
};
const std::map<std::string, int(Person::*)() const> Person::intMethodMap = {
{"age", &Person::get_age}, {"height", &Person::get_height}, {"years_left()", &Person::years_left}
};
const std::map<std::string, bool(Person::*)() const> Person::boolMethodMap = {
{"can_vote()", &Person::can_vote}, {"can_quit_school()", &Person::can_quit_school}
};
const std::map<std::string, std::string(Person::*)() const> Person::stringMethodMap = {
{"name", &Person::get_name}, {"country", &Person::get_country}, {"zip_code", &Person::get_zip_code}, {"nickname", &Person::nickname}
};
const Person::TupleMaps Person::methodMaps = std::make_tuple(Person::intMethodMap, Person::boolMethodMap, Person::stringMethodMap);
struct ConsolePersonObserver : Observer<Person> {
virtual void field_changed (Person& source, const std::string& field_name, OutputType output_type) override { field_changed_helper<FirstOutputType>(source, field_name, output_type); }
private:
template <OutputType> void field_changed_helper (Person& source, const std::string& field_name, OutputType output_type);
};
template <OutputType N>
void ConsolePersonObserver::field_changed_helper (Person& source, const std::string& field_name, OutputType output_type) {
if (output_type == N)
std::cout << source.get_name() << "'s " << field_name << " has changed to " << (source.*std::get<N>(Person::methodMaps).at(field_name))() << ".\n";
else
field_changed_helper<static_cast<OutputType>(N+1)>(source, field_name, output_type);
}
template <>
void ConsolePersonObserver::field_changed_helper<NumOutputTypes> (Person&, const std::string&, OutputType) { } // End of recursion
template <typename T>
void Person::set_field (T Person::*ptr, const std::string& field_name, const T& value) {
if (value == this->*ptr)
return;
std::tuple<std::map<std::string, int>, std::map<std::string, bool>, std::map<std::string, std::string>> trackedValuesAllTypes;
for (const std::pair<std::string, OutputType>& pair : dependenciesMap.at(field_name))
set_field_helper<FirstOutputType>(trackedValuesAllTypes, pair);
this->*ptr = value;
const OutputType type = GetOutputType<typename std::remove_reference<decltype(this->*ptr)>::type>::value;
notify_field_change(*this, type, field_name);
notify_other_field_changes<FirstOutputType>(trackedValuesAllTypes);
}
template <OutputType N>
void Person::set_field_helper (TupleMaps2& trackedValuesAllTypes, const std::pair<std::string, OutputType>& pair) {
if (pair.second == N)
std::get<N>(trackedValuesAllTypes)[pair.first] = (this->*std::get<N>(Person::methodMaps).at(pair.first))();
else
set_field_helper<static_cast<OutputType>(N+1)>(trackedValuesAllTypes, pair);
}
template <>
void Person::set_field_helper<NumOutputTypes> (TupleMaps2&, const std::pair<std::string, OutputType>&) { } // End of recursion.
template <OutputType N>
void Person::notify_other_field_changes (const TupleMaps2& trackedValuesAllTypes) {
for (const auto& pair : std::get<N>(trackedValuesAllTypes)) {
if (pair.second != (this->*std::get<N>(methodMaps).at(pair.first))())
notify_field_change(*this, N, pair.first);
}
notify_other_field_changes<static_cast<OutputType>(N+1)>(trackedValuesAllTypes);
}
template <>
void Person::notify_other_field_changes<NumOutputTypes> (const TupleMaps2&) { } // End of recursion.
int main() {
Person p("Tom", 14, "USA", "90210");
ConsolePersonObserver cpo;
p.subscribe(&cpo);
p.set_age(15);
p.set_age(16);
p.set_zip_code("999999");
p.set_country("France");
}
Output:
Tom's age has changed to 15.
Tom's years_left() has changed to 65.
Tom's can_quit_school() has changed to 1.
Tom's age has changed to 16.
Tom's years_left() has changed to 64.
Tom's can_vote() has changed to 1.
Tom's zip_code has changed to 999999.
Tom's can_quit_school() has changed to 0.
Tom's country has changed to France.
Tom's years_left() has changed to 10.
Tom's can_vote() has changed to 0.
Tom's nickname has changed to Francois.