views:

604

answers:

5

In my epic quest of making C++ do things it shouldn't, I am trying to put together a compile time generated class.

Based on a preprocessor definition, such as (rough concept)

CLASS_BEGIN(Name)  
    RECORD(xyz)  
    RECORD(abc)

    RECORD_GROUP(GroupName)  
        RECORD_GROUP_RECORD(foo)  
        RECORD_GROUP_RECORD(bar)  
    END_RECORDGROUP   
END_CLASS

While I am fairly sure I generate a class that reads the data from the file system using this sort of structure (Maybe even doing it using Template Metaprogramming), I don't see how I can generate both the functions to access the data and the function to read the data.

I would want to end up with a class something like this

class Name{
    public:
    xyz_type getxyz();
    void setxyz(xyz_type v);

    //etc

    list<group_type> getGroupName();

    //etc

    void readData(filesystem){
         //read xyz
         //read abc
         //etc
    }
};

Does anyone have any idea if this is even possible?

--EDIT--

To clarify the intended usage for this. I have files in a standard format I want to read. The format is defined already, so it is not open to change. Each file can contain any number records, each of which can contain any number sub records.

The numerous record types each contain a diffrent set of sub records, but they can be are defined. So for example the Heightmap record must contain a Heightmap, but can optional contain normals.

So I would want to define a Record for that like so:

CLASS_BEGIN(Heightmap)  
    RECORD(VHDT, Heightmap, std::string) //Subrecord Name, Readable Name, Type  
    RECORD_OPTIONAL(VNML, Normals, std::string)  
END_CLASS  

For which I would want to output something with the functionality of a class like this:

class Heightmap{
    public:
    std::string getHeightmap(){
        return mHeightmap->get<std::string>();
    }
    void setHeightmap(std::string v){
        mHeight->set<std::string>(v);
    }

    bool hasNormal(){
        return mNormal != 0;
    }
    //getter and setter functions for normals go here

    private:
    void read(Record* r){
        mHeightmap = r->getFirst(VHDT);
        mNormal = r->getFirst(VNML);
    }


    SubRecord* mHeightmap, mNormal;
}

The issue I am having is that I need every preprocessor definition twice. Once for defining the function definition within the class, and once for creating the read function. As the preprocessor is purely functional, I cannot push the data to a queue and generate the class on the END_CLASS marco definition.

I cannot see a way around this issue, but wondered if anyone who has a greater understanding of C++ did.

+7  A: 

If you are looking for a way to serialize/deserialize data with C++ code generation, I would look at Google protobufs (http://code.google.com/p/protobuf/) or Facebook's Thrift (http://incubator.apache.org/thrift/).

For protobufs, you write a data definition like so:

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

A Person C++ class is then generated that lets you load, save and access this data. You can also generate python, java, etc.

Joe Beda
+2  A: 

I might play around with a record mixin to do something similar -- add functionality to a class automagically at compile time

   template<class Base, class XyzRecType>
   class CRecord : public Base
   {
   protected:
      RecType xyz;
   public:
      CRecord() : Base() {}


      RecType Get() {return xyz;}

      void Set(const RecType& anXyz) {xyz = anXyz;}

      void ReadFromStream( std::istream& input)
      {
           ...
      }

   };

   class CMyClass
   {
   };

   int main()
   {
        // now thanks to the magic of inheritance, my class has added methods!
        CRecord<CMyClass, std::string> myClassWithAStringRecord;

        myClassWithAStringRecord.Set("Hello");

   }
Doug T.
A: 

I'm not exactly sure what you're looking for in some cases.

  • What happens to foo and bar in the specification?
  • What does getGroupName actually return? (foo,bar)? or GroupName?

It looks like you're trying to create a mechanism for loading and accessing on-disk structures of arbitrary layout. Is this accurate? (Edit: Just noticed the "set" member function... so I guess you're looking for full serialization)

If you're on a *nix system, specifying your own compiler to compile to .o (likely a perl/python/what-have-you script that finishes with a call to gcc) in the Makefile is a trivial solution. Others might know of ways of doing this on windows.

jkerian
+3  A: 

You might be able to solve this problem using boost tuples. It will result in a design which is different to what you are thinking of now, but it should allow you to solve the problem in a generic way.

The following example defines a record of the form "std::string,bool" and then reads that data in from a stream.

#include "boost/tuple/tuple.hpp"
#include <iostream>
#include <sstream>

using namespace ::boost::tuples;

The functions are used to read the data from an istream. The first overload stops the iteration through the tuple after we reach the last record type:

//
// This is needed to stop when we have no more fields
void read_tuple (std::istream & is, boost::tuples::null_type )
{
}

template <typename TupleType>
void read_tuple (std::istream & is, TupleType & tuple)
{
  is >> tuple.template get_head ();
  read_tuple (is, tuple.template get_tail ());
}

The following class implements the getter member for our Record. Using the RecordKind as our key we get the specific member that we're interested in.

template <typename TupleType>
class Record
{
private:
  TupleType m_tuple;

public:
  //
  // For a given member - get the value
  template <unsigned int MBR>
  typename element <MBR, TupleType>::type & getMember ()
  {
    return m_tuple.template get<MBR> ();
  }

  friend std::istream & operator>> (std::istream & is
                                  , Record<TupleType> & record)
  {
    read_tuple (is, record.m_tuple);
  }
};

The next type is the meta description for our record. The enumeration gives us a symbolic name that we can use to access the members, ie. the field names. The tuple then defines the types of those fields:

struct HeightMap
{
  enum RecordKind
  {
    VHDT
    , VNML
  };

  typedef boost::tuple < std::string
                       , bool
                     > TupleType;
};

Finally, we construct a record and read in some data from a stream:

int main ()
{
  Record<HeightMap::TupleType> heightMap;
  std::istringstream iss ( "Hello 1" );

  iss >> heightMap;

  std::string s = heightMap.getMember < HeightMap::VHDT > ();
  std::cout << "Value of s: " << s << std::endl;


  bool b = heightMap.getMember < HeightMap::VNML > ();
  std::cout << "Value of b: " << b << std::endl;
}

And as this is all template code, you should be able to have records nested in records.

Richard Corden
+1  A: 

This is a technique I use a lot in C and C++, called "list macro". Suppose you have a list of things like variables, error messages, interpreter opcodes, or anything about which repetitive code needs to be written. In your case it is class member variables.

Suppose it is variables. Put them in a list macro like this:

#define MYVARS \
DEFVAR(int, a, 6) \
DEFVAR(double, b, 37.3) \
DEFARR(char, cc, 512) \

To declare the variables, do this:

#define DEFVAR(typ,nam,inival) typ nam = inival;
#define DEFARR(typ,nam,len) typ nam[len];
  MYVARS
#undef  DEFVAR
#undef  DEFARR

Now you can generate any sort of repetitive code just by redefining DEFVAR and DEFARR, and instantiating MYVARS.

Some people find this rather jarring, but I think it's a perfectly good way to use the preprocessor as a code generator, and accomplish DRY. And, the list macro itself becomes a mini-DSL.

Mike Dunlavey
In C, yes. Often in C++ I'll look for a TMP solution first. This might go some way to explain why I'm more productive in C.
Pete Kirkham
My preference between C and C++ depends on what I'm doing, but I completely understand.
Mike Dunlavey