Last active
August 29, 2015 14:04
-
-
Save mbasaglia/79feb491ac842970d0e7 to your computer and use it in GitHub Desktop.
C++ Class Properties Hack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <map> | |
#include <functional> | |
#include <sstream> | |
namespace misc { | |
/** | |
* \brief Abstract class for classes that have some way to access properties | |
* | |
* Inheriting this class and using the proper macros, derived classes can access | |
* some members via strings. | |
* | |
* \code {.cpp} | |
class A : public Object_With_Properties | |
{ | |
PROPERTY_MAGIC(A) | |
DECLARE_PROPERTY(A,string,get_string,set_string) | |
DECLARE_PROPERTY_GETTER(A,int,number) | |
DECLARE_PROPERTY_GET_FUNCTOR(A,int2,[](const A& self){return std::to_string(self.number()-789);}) | |
DECLARE_PROPERTY_ATTRIBUTE(A,foo) | |
// Allow some properties to be accessed by other means | |
std::string access_undeclared_property(const std::string& name) const override { return name == "bar" ? "Bar" : ""; } | |
bool has_undeclared_property(const std::string& name) const override { return name == "bar"; } | |
public: | |
std::string get_string() const; | |
void set_string(const std::string&) const; | |
int number() const; | |
double foo; | |
}; \endcode | |
* \note DECLARE_PROPERTY_* macros are best if called outside the class, | |
* in an implementation file inside an unnamed namespace. | |
* This will avoid unnecessary overhead during object construction. | |
*/ | |
class Object_With_Properties | |
{ | |
protected: | |
/** | |
* \brief Magic class used by macros, do not use | |
*/ | |
struct PROPERTY_MAGIC_CLASS | |
{ | |
virtual bool has( const std::string& property) const { return false; } | |
virtual bool has_writable( const std::string& property) const { return false; } | |
virtual std::string get(const Object_With_Properties& obj, const std::string& property) const {return "";} | |
virtual void set(Object_With_Properties& obj, const std::string& property, const std::string& value) const {} | |
static PROPERTY_MAGIC_CLASS& instance() { static PROPERTY_MAGIC_CLASS s; return s; } | |
protected: | |
PROPERTY_MAGIC_CLASS() {} | |
PROPERTY_MAGIC_CLASS(const PROPERTY_MAGIC_CLASS &) = delete; | |
}; | |
/** | |
* \brief Method used internally to get the dynamic magic object | |
*/ | |
virtual const PROPERTY_MAGIC_CLASS& get_magic() const = 0; | |
/** | |
* \brief Override if you want some extra properties | |
*/ | |
virtual std::string get_undeclared_property(const std::string& name) const { return ""; } | |
/** | |
* \brief Override if you want some extra properties | |
*/ | |
virtual bool has_undeclared_property(const std::string& name) const { return false; } | |
/** | |
* \brief Override if you want some extra writable properties | |
*/ | |
virtual void set_undeclared_property(const std::string& name, const std::string& value) const { return; } | |
/** | |
* \brief Override if you want some extra writable properties | |
*/ | |
virtual bool has_writable_undeclared_property(const std::string& name) const { return false; } | |
public: | |
/** | |
* \brief Get property by name | |
* \param name Name of the property | |
* \return The property value or an empty string if there's no property with that given name | |
*/ | |
std::string get_property(const std::string& name) const | |
{ | |
return get_magic().has(name) ? get_magic().get(*this,name) : get_undeclared_property(name); | |
} | |
/** | |
* \brief Set property by name | |
* \param name Name of the property | |
* \param value Value as string | |
*/ | |
void set_property(const std::string&name, const std::string& value) | |
{ | |
return get_magic().has_writable(name) ? get_magic().set(*this,name,value) : set_undeclared_property(name,value); | |
} | |
/** | |
* \brief Whether a property exists | |
* \param name Name of the property | |
*/ | |
bool has_property(const std::string& name) const | |
{ | |
return get_magic().has(name) || has_undeclared_property(name); | |
} | |
/** | |
* \brief Whether a writable property exists | |
* \param name Name of the property | |
*/ | |
bool has_writable_property(const std::string& name) const | |
{ | |
return get_magic().has_writable(name) || has_writable_undeclared_property(name); | |
} | |
}; | |
/** | |
* \brief Enable property magic for classes that don't inherit Object_With_Properties directly | |
* \param c Class | |
* \param base Base class, must be derived from Object_With_Properties | |
*/ | |
#define PROPERTY_MAGIC_INHERIT(c,base) \ | |
public: struct PROPERTY_MAGIC_CLASS : base::PROPERTY_MAGIC_CLASS { \ | |
std::map<std::string,std::function<std::string (const misc::Object_With_Properties&)>> getters; \ | |
std::map<std::string,std::function<void (misc::Object_With_Properties&, const std::string&)>> setters; \ | |
std::string get(const misc::Object_With_Properties& obj, const std::string& property) const override { \ | |
auto it = getters.find(property); \ | |
return it != getters.end() ? it->second(obj) : base::PROPERTY_MAGIC_CLASS::instance().get(obj,property); } \ | |
bool has(const std::string& property) const override { \ | |
return getters.find(property) != getters.end() || base::PROPERTY_MAGIC_CLASS::instance().has(property); } \ | |
virtual void set(misc::Object_With_Properties& obj, const std::string& property, const std::string& value) const override {\ | |
auto it = setters.find(property); \ | |
return it != setters.end() ? it->second(obj,value) : base::PROPERTY_MAGIC_CLASS::instance().set(obj,property,value); }\ | |
bool has_writable(const std::string& property) const override { \ | |
return setters.find(property) != setters.end() || base::PROPERTY_MAGIC_CLASS::instance().has_writable(property); } \ | |
static PROPERTY_MAGIC_CLASS& instance() { static PROPERTY_MAGIC_CLASS s; return s; } \ | |
template<class T> static T setter_argument(const std::string& value, void(c::*)(T)) \ | |
{ T v; std::istringstream(value) >> v; return v; } \ | |
const static std::string& setter_argument(const std::string& value,void(c::*)(const std::string&)) \ | |
{ return value; } \ | |
template<class T> static T setter_value(const std::string& value, const T&) \ | |
{ T v; std::istringstream(value) >> v; return v; } \ | |
const static std::string& setter_value(const std::string& value,const std::string&) \ | |
{ return value; } \ | |
}; \ | |
const misc::Object_With_Properties::PROPERTY_MAGIC_CLASS& get_magic() const override { \ | |
return PROPERTY_MAGIC_CLASS::instance(); } \ | |
static PROPERTY_MAGIC_CLASS& get_static_magic() { return PROPERTY_MAGIC_CLASS::instance(); } \ | |
private: | |
/** | |
* \brief Enable property magic | |
* \param c Class | |
*/ | |
#define PROPERTY_MAGIC(c) PROPERTY_MAGIC_INHERIT(c,misc::Object_With_Properties) | |
/** | |
* \brief Declare a property read using a functor | |
* \param c Class | |
* \param name Property name as a C++ symbol | |
* \param functor Functor returning a std::string | |
*/ | |
#define DECLARE_PROPERTY_GET_FUNCTOR(c,name,functor) \ | |
struct PROPERTY_MAGIC_CLASS_GET_##c_##name { \ | |
PROPERTY_MAGIC_CLASS_GET_##c_##name() { \ | |
c::get_static_magic().getters[#name] = [](const misc::Object_With_Properties& obj){ \ | |
return ((functor)((c&)obj)); }; }\ | |
} PROPERTY_MAGIC_OBJECT_GET_##c_##name; | |
/** | |
* \brief Declare a property read using a getter method | |
* \param c Class | |
* \param name Property name as a C++ symbol | |
* \param method Name of the method used to access the property | |
* \note \c c::method must return a type which overload the stream operator << | |
*/ | |
#define DECLARE_PROPERTY_GETTER(c,name,method) DECLARE_PROPERTY_GET_FUNCTOR(c,name, \ | |
[](const c& obj){ std::stringstream ss; ss << obj.method(); return ss.str();} ) | |
/** | |
* \brief Declare a property written using a functor | |
* \param c Class | |
* \param name Property name as a C++ symbol | |
* \param functor Functor returning a std::string | |
*/ | |
#define DECLARE_PROPERTY_SET_FUNCTOR(c,name,functor) \ | |
struct PROPERTY_MAGIC_CLASS_SET_##c_##name { \ | |
PROPERTY_MAGIC_CLASS_SET_##c_##name() { \ | |
c::get_static_magic().setters[#name] = [](misc::Object_With_Properties& obj, const std::string& value){ \ | |
return ((functor)((c&)obj,value)); }; }\ | |
} PROPERTY_MAGIC_OBJECT_SET_##c_##name; | |
/** | |
* \brief Declare a property written using a setter method | |
* \param c Class | |
* \param name Property name as a C++ symbol | |
* \param method Name of the method used to access the property | |
* \note \c c::method must take a type which overload the stream operator >> and return void | |
*/ | |
#define DECLARE_PROPERTY_SETTER(c,name,method) DECLARE_PROPERTY_SET_FUNCTOR(c,name, \ | |
[](c& obj, const std::string& value){ obj.method(c::PROPERTY_MAGIC_CLASS::setter_argument(value,&c::method));} ) | |
/** | |
* \brief Declare a property read and written using accessor methods | |
* \param c Class | |
* \param name Property name as a C++ symbol | |
* \param getter Getter method | |
* \param setter Setter method | |
*/ | |
#define DECLARE_PROPERTY(c,name,getter,setter) DECLARE_PROPERTY_GETTER(c,name,getter) DECLARE_PROPERTY_SETTER(c,name,setter) | |
/** | |
* \brief Declare a property accessible directly with an attribute | |
* \param c Class | |
* \param name property/attribute name | |
* \note \c c::name must be of a type which overload the stream operator << and >> | |
*/ | |
#define DECLARE_PROPERTY_ATTRIBUTE(c,name) DECLARE_PROPERTY_GET_FUNCTOR(c,name, \ | |
[](const c& obj){ std::stringstream ss; ss << obj.name; return ss.str();} ) \ | |
DECLARE_PROPERTY_SET_FUNCTOR(c,name,[](c&obj,const std::string&value) { \ | |
obj.name = c::PROPERTY_MAGIC_CLASS::setter_value(value,obj.name); }) | |
} // namespace | |
using namespace misc; | |
class A : public Object_With_Properties | |
{ | |
protected: | |
PROPERTY_MAGIC(A) | |
DECLARE_PROPERTY(A,string,string,set_string) | |
DECLARE_PROPERTY_GETTER(A,int,number) | |
std::string get_undeclared_property(const std::string& name) const override { return "Bar"; } | |
bool has_undeclared_property(const std::string& name) const override { return name == "bar"; } | |
public: | |
std::string s; | |
A(std::string s) : s(s) {} | |
void set_string(const std::string& st) { s = st; } | |
std::string string() const { return s; } | |
int number() const { return 1234789; } | |
}; | |
class B : public A | |
{ | |
protected: | |
PROPERTY_MAGIC_INHERIT(B,A) | |
void set_undeclared_property(const std::string& name, const std::string& value) const override { | |
std::cout << "B: " << name << " = " << value << '\n'; | |
} | |
public: | |
B(std::string s) : A(s) {} | |
float real = 89; | |
void set_real(float x) { real = x; } | |
float get_real() const { return real; } | |
}; | |
namespace { | |
//DECLARE_PROPERTY(B,real,get_real,set_real) | |
DECLARE_PROPERTY_ATTRIBUTE(B,real) | |
DECLARE_PROPERTY_GET_FUNCTOR(B,test,[](const A& self){return "lol";}) | |
DECLARE_PROPERTY_GET_FUNCTOR(A,int2,[](const A& self){return std::to_string(self.number()- 789);}) | |
DECLARE_PROPERTY_ATTRIBUTE(A,s) | |
} | |
int main() | |
{ | |
B a("Foo"); | |
//A& a = b; | |
std::cout << a.get_property("string") << a.get_property("int") | |
<< a.get_property("int2") << a.get_property("nothing") | |
<< a.get_property("s") << a.get_property("test") << "\n"; | |
std::cout << a.has_property("string") << a.has_property("nothing") << a.has_property("bar") << "\n"; | |
std::cout << a.get_property("real") << a.get_property("string") << '\n'; | |
a.set_property("real","123"); | |
a.set_property("fake","123"); | |
a.set_property("string","---"); | |
std::cout << a.get_property("real") << a.get_property("string") << '\n'; | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment