Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a good way of setting C/C++ member variables from string representations? (introspection-lite)

I've got a struct with some members that I want to be able to get and set from a string. Given that C++ doesn't have any introspection I figure I need some creative solution with macros, the stringize operator and maybe boost::bind. I don't need full serialization or introspection, more an 'introspection-lite'

I'd like to have something along the lines of this:

struct MyType {
  int  fieldA;
  int  fieldB;
};
DECLARE_STRING_MAP(MyType,fieldA);
DECLARE_STRING_MAP(MyType,fieldB);

MyType t;
SET_VALUE_FROM_STRING(MyType,t,"fieldA","3")    

Rather than have a huge if statement.

Any idea if there's a neat solution to this?

Related question: Object Reflection

EDIT: Thanks to maxim1000 for the 'map to int Type::*' trick -- this worked for me:

#define DEFINE_LOOKUP_MAP(Type) std::map<AnsiString,int Type::*> mapper 
#define ADD_FIELD_MAPPING(Type, Field) mapper[#Field]=&Type::Field 
#define SET_FIELD_FROM_MAP(Type, Field, var, value) var.*(mapper[#Field])=value    

DEFINE_LOOKUP_MAP(MyType); 
ADD_FIELD_MAPPING(MyType, fieldA); 
ADD_FIELD_MAPPING(MyType, fieldB); 

SET_FIELD_FROM_MAP(MyType, fieldA, obj, 3);
like image 825
the_mandrill Avatar asked Nov 06 '09 14:11

the_mandrill


5 Answers

If all of them have the same type, you can use something like this:

std::map<std::string,int MyType::*> mapper;
mapper["fieldA"]=&MyType::fieldA;
mapper["fieldB"]=&MyType::fieldB;
...
MyType obj;
obj.*(mapper["fieldA"])=3;
like image 85
maxim1000 Avatar answered Nov 15 '22 02:11

maxim1000


Death to macros.

Some macro-free code i've used in the past for binding names to struc members and converting non-string types to string:

#include <map>
#include <string>
#include <sstream>

template<class STRUC>
struct Field
{
    virtual void set (STRUC& struc, const std::string& value) const = 0;
};

template<class STRUC, class FIELDTYPE>
struct FieldImpl : public Field<STRUC>
{
    typedef FIELDTYPE (STRUC::*MemberPtr);

    FieldImpl (MemberPtr memberPtr) {memberPtr_ = memberPtr;}

    virtual void set (STRUC& struc, const std::string& value) const
    {
        std::istringstream iss (value);
        iss >> struc.*memberPtr_;
    }

private:
    MemberPtr memberPtr_;
};

template<class STRUC>
class FieldMap
{
private:
    typedef std::map<std::string, Field<STRUC>*> FieldNameMap;
    FieldNameMap  fieldMap_;

public:
    ~FieldMap ()
    {
        // delete fieldMap_ members.
    }

    void bind (const std::string& name, Field<STRUC>* field)
    {
        fieldMap_[name] = field;
    }

    template<typename FIELDTYPE>
    void bind (const std::string& name, FIELDTYPE (STRUC::* member))
    {
        fieldMap_[name] = new FieldImpl<STRUC, FIELDTYPE> (member);
    }

    void setValue (STRUC& struc, const std::string& name, const std::string& value)
    {
        FieldNameMap::const_iterator iter = fieldMap_.find (name);

        if (iter == fieldMap_.end ())
            throw std::runtime_error (std::string ("No field binding found for ") + name);

        (*iter).second->set (struc, value);
    }
};

struct Test
{
    int id;
    double value;
    std::string tag;
};

int main (int argc, char* argv[])
{
    FieldMap<Test> fieldMap;
    fieldMap.bind ("id", &Test::id);
    fieldMap.bind ("value", &Test::value);
    fieldMap.bind ("tag", &Test::tag);

    Test test;

    fieldMap.setValue (test, "id", "11");
    fieldMap.setValue (test, "value", "1234.5678");
    fieldMap.setValue (test, "tag", "hello");

    return 0;
}
like image 34
jon-hanson Avatar answered Nov 15 '22 00:11

jon-hanson


I can think of two solutions.

Use macros to create a structure definition and its map from the same source

By using macros and reworking your structure definition, you can use the technique as described in this excellent answer without separately declaring the map.

Rewrite your structure definition like this, and put it in a header by itself:

BEGIN_STRUCT(MyType)
FIELD(int, fieldA);
FIELD(int, fieldB);
END_STRUCT

Then #include it twice. Before #including it the first time:

#define BEGIN_STRUCT(x) struct x {
#define FIELD(x, y) x y;
#define END_STRUCT };

Before #including it the second time:

#define BEGIN_STRUCT(x) namespace x ## Mapping { typedef x MappedType;
#define FIELD mapper[#x]=&MappedType::x;
#define END_STRUCT }

I've not tested this, so a few details may be off.

If macros are banned in your environment, you could instead create the structure definition and its map from whatever external tool you wish (Perl, Python's Cog, etc.).

Use a reflection library for C++

Although C++ doesn't directly implement reflection or introspection, add-on libraries are available. I've used ROOT's Reflex library with good results.

like image 22
Josh Kelley Avatar answered Nov 15 '22 00:11

Josh Kelley


If you aren't willing to change the struct to something else, you really don't have a choice - you're going to need the big if statement to tell which field you're dealing with. You can hide it (and make it easier to write) with macros, but it's the same structure, and you're going to have to just deal with it.

Here's an example of how you might write that macro - it does make usage simpler, but it's still not 'short' by any means.

//Assumption: at the time you want to use this, you've got two strings, one with the 
// name of the field to set (key), one with the value to set (value). I also assume

typedef struct MyType {
  int  fieldA;
  int  fieldB;
} MyType;

// fldnamedef - name of the field in the structure definition (const char *)
// convfunc - name of a function that takes a value, returns a fldtypedef
// s - structure to put data into
// key - const char * pointing to input field name
// value - const char * pointing to input field value
#define IF_FIELD_SET(fldnamedef, convfunc, s,  key, value) {\
  if (strcmp(#fldnamedef, key) == 0) {\
    s.fldnamedef = convfunc(value);\
  }\
}


int main()
{
  MyType t={0,0};

  IF_FIELD_SET(fieldA, atoi, t, "fieldA", "2");

  printf("%d,%d\n",t.fieldA, t.fieldB);
}

And here's the pre-processor output that the IF_FIELD_SET line turns into:

{ if (strcmp("fieldA", "fieldA") == 0) { t.fieldA = atoi("2"); }};
like image 1
Michael Kohne Avatar answered Nov 15 '22 02:11

Michael Kohne


Introspection emulation ? That sounds like a challenge, that's for sure.

The interface does not really please me, so I would propose an alternative:

struct MyType
{
  int fieldA;
  int fieldB;

  void setField(std::string const& field, std::string const& value);
};

Now the challenge is for setField to select the right field, and indeed a map seems appropriate. However we need to encapsulate the type information somewhere (unless you plan on using only ints, in which case... there is no difficulty), so a map of functors is in order.

static std::map<std::string, Functor<MyType>*> M_Map;

// where Functor is

template <class Type>
struct Functor
{
  virtual void set(Type& t, std::string const& value) const = 0;
};

// And a specialization would be
struct SetfieldA : public Functor<MyType>
{
  virtual void set(MyType& t, std::string const& value) const
  {
    std::istringstream stream(value);
    stream >> t.fieldA;
    // some error handling could be welcome there :)
  }
};

Note the use of std::istringstream, now you can support any type as long as they correctly interact with an std::istream. Thus you can support user defined classes.

And of course, the part here is all about automation!

And automation like in macros.

#define INTROSPECTED(MyType_)                                                    \
  private:                                                                       \
    typedef Functor<MyType_> intro_functor;                                      \
    typedef std::map<std::string, intro_functor const*> intro_map;               \
    static intro_map& IntroMap() { static intro_map M_; return M_; }             \
  public:                                                                        \
    static void IntroRegister(std::string const& field, intro_functor const* f){ \
      IntroMap()[field] = f; }                                                   \
    void setField(std::string const& field, std::string const& value) {          \
      intro_map::const_iterator it = IntroMap().find(field);                     \
      if (it != IntroMap().end()) it->second->set(*this, value); }

#define INTROSPECT_FIELD(Class_, Name_)                                          \
  struct Set##Name_: public Functor<Class_> {                                    \
    virtual void set(Class_& t, std::string const& value) {                      \
      std::istringstream stream(value); stream >> t.Name_; } } Setter##Name_;    \
  Class_::IntroRegister(#Name_, Setter##Name_)

Usage like this:

// myType.h
struct MyType
{
  INTROSPECTED(MyType);

  int fieldA;
  int fieldB;
};

// myType.cpp
INTROSPECT_FIELD(MyType, fieldA);
INTROSPECT_FIELD(MyType, fieldB);

// Any file
MyType t;
t.set("fieldA", "3");

Of course the usual caveat apply: off the top of my head, never compiled it, may kill kittens and worse.

like image 1
Matthieu M. Avatar answered Nov 15 '22 01:11

Matthieu M.