Skip to content

Instantly share code, notes, and snippets.

@mbasaglia
Last active August 29, 2015 14:04
Show Gist options
  • Save mbasaglia/79feb491ac842970d0e7 to your computer and use it in GitHub Desktop.
Save mbasaglia/79feb491ac842970d0e7 to your computer and use it in GitHub Desktop.
C++ Class Properties Hack
#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