Генерация кода C++

В своем эпическом стремлении заставить C++ делать то, чего он не должен, я пытаюсь собрать класс, сгенерированный во время компиляции.

На основе определения препроцессора, такого как (грубая концепция)

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

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

Хотя я совершенно уверен, что сгенерирую класс, который считывает данные из файловой системы, используя такую ​​структуру (возможно, даже делая это с помощью метапрограммирования шаблонов), я не понимаю, как я могу сгенерировать как функции для доступа к данным, так и функция чтения данных.

Я хотел бы закончить с классом что-то вроде этого

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
    }
};

Кто-нибудь знает, возможно ли это вообще?

--РЕДАКТИРОВАТЬ--

Чтобы уточнить предполагаемое использование для этого. У меня есть файлы в стандартном формате, которые я хочу прочитать. Формат уже определен, поэтому его нельзя изменить. Каждый файл может содержать любое количество записей, каждая из которых может содержать любое количество подзаписей.

Каждый из многочисленных типов записей содержит различный набор вложенных записей, но их можно определить. Так, например, запись карты высот должна содержать карту высот, но может содержать нормали.

Поэтому я хотел бы определить запись для этого так:

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

Для чего я хотел бы вывести что-то с функциональностью класса, например:

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;
}

Проблема, с которой я сталкиваюсь, заключается в том, что мне нужно каждое определение препроцессора дважды. Один раз для определения определения функции в классе и один раз для создания функции чтения. Поскольку препроцессор является чисто функциональным, я не могу помещать данные в очередь и генерировать класс в определении маркировки END_CLASS.

Я не вижу способа обойти эту проблему, но задавался вопросом, знает ли кто-нибудь, кто лучше разбирается в С++.


person Yacoby    schedule 05.06.2009    source источник
comment
Это возможно, я подозреваю. Можете ли вы быть более точным в отношении того, где именно вы упираетесь в стену?   -  person i_am_jorf    schedule 05.06.2009
comment
Неясно, какие преимущества вы получаете от этого - объясните, пожалуйста.   -  person    schedule 05.06.2009


Ответы (6)


Возможно, вы сможете решить эту проблему, используя кортежи boost . Это приведет к дизайну, который отличается от того, о чем вы думаете сейчас, но он должен позволить вам решить проблему в общем виде.

В следующем примере определяется запись формы "std::string,bool", а затем считываются эти данные из потока.

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

using namespace ::boost::tuples;

Функции используются для чтения данных из istream. Первая перегрузка останавливает итерацию по кортежу после того, как мы достигаем последнего типа записи:

//
// 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 ());
}

Следующий класс реализует элемент получения для нашей записи. Используя RecordKind в качестве нашего ключа, мы получаем конкретный член, который нас интересует.

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);
  }
};

Следующий тип — это метаописание для нашей записи. Перечисление дает нам символическое имя, которое мы можем использовать для доступа к элементам, т.е. имена полей. Затем кортеж определяет типы этих полей:

struct HeightMap
{
  enum RecordKind
  {
    VHDT
    , VNML
  };

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

Наконец, мы создаем запись и считываем некоторые данные из потока:

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;
}    

И поскольку это весь код шаблона, вы должны иметь возможность вкладывать записи в записи.

person Richard Corden    schedule 08.06.2009

Если вы ищете способ сериализации/десериализации данных с помощью генерации кода C++, я бы посмотрел на протобуфы Google (http://code.google.com/p/protobuf/) или Thrift Facebook (http://incubator.apache.org/thrift/).

Для protobufs вы пишете определение данных следующим образом:

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;
}

Затем создается класс Person C++, который позволяет загружать, сохранять и получать доступ к этим данным. Вы также можете сгенерировать python, java и т. д.

person Joe Beda    schedule 05.06.2009

Это метод, который я часто использую в C и C++, называется "макрос списка". Предположим, у вас есть список таких вещей, как переменные, сообщения об ошибках, коды операций интерпретатора или что-то еще, для чего нужно написать повторяющийся код. В вашем случае это переменные-члены класса.

Допустим, это переменные. Поместите их в макрос списка следующим образом:

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

Чтобы объявить переменные, сделайте следующее:

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

Теперь вы можете генерировать любой повторяющийся код, просто переопределив DEFVAR и DEFARR и создав экземпляр MYVARS.

Некоторых это раздражает, но я думаю, что это отличный способ использовать препроцессор в качестве генератора кода и выполнить DRY. И сам макрос списка становится мини-DSL.

person Mike Dunlavey    schedule 10.06.2009
comment
В С, да. Часто в C++ я сначала ищу решение TMP. Это может как-то объяснить, почему я более продуктивно работаю на C. - person Pete Kirkham; 14.01.2010
comment
Мое предпочтение между C и C++ зависит от того, что я делаю, но я полностью понимаю. - person Mike Dunlavey; 14.01.2010

Я мог бы поэкспериментировать с миксином записи, чтобы сделать что-то подобное — автоматически добавить функциональность в класс во время компиляции.

   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");

   }
person Doug T.    schedule 05.06.2009

В общем, вы можете добиться именно того, что хотите, если объедините все в один макрос, а затем используете библиотеку препроцессора Booost для определения своего класса. Посмотрите, как я реализовал макрос MACE_REFLECT, который выполняет частичную специализацию всего класса и должен ссылаться на каждое имя дважды в разных частях.

Это очень похоже на то, как я автоматически разбираю JSON в структуры с помощью препроцессора.

Учитывая ваш пример, я бы перевел его так:

struct Name {
   xyz_type xyz;
   abc_type abc;
   boost::optional<foo_type> foo;
   boost::optional<bar_type> bar;
};
MACE_REFLECT( Name, (xyz)(abc)(foo)(bar) )

Теперь я могу «посетить» членов Name из моего парсера:

struct visitor {
  template<typename T, T  p>
  inline void operator()( const char* name )const {
        std::cout << name << " = " << c.*p;
  }
  Name c;
};
mace::reflect::reflector<Name>::visit(visitor());

Если ваши объекты могут быть представлены в виде структур, массивов, пар ключ-значение и примитивов, то этот метод творит чудеса и дает мне мгновенную сериализацию/десериализацию в/из json/xml или вашего пользовательского формата записи.

https://github.com/bytemaster/mace/blob/master/libs/rpc/examples/jsonv.cpp

person bytemaster    schedule 29.07.2012
comment
Проголосовал за его крутую злобность, но, AFAICU (HYGWIMBTA = Надеюсь, вы поняли, что я имел в виду под этим сокращением), он не хотел писать имена полей дважды. Ваш контакт. макрос требует именно этого. - person Sz.; 29.10.2015

Я не совсем уверен, что вы ищете в некоторых случаях.

  • Что происходит с foo и bar в спецификации?
  • Что на самом деле возвращает getGroupName? (фу, бар)? или имя группы?

Похоже, вы пытаетесь создать механизм для загрузки и доступа к структурам произвольной компоновки на диске. Это точно? (Редактировать: только что заметил функцию-член "set"... так что я думаю, вы ищете полную сериализацию)

Если вы работаете в системе *nix, указание собственного компилятора для компиляции в .o (вероятно, сценарий perl/python/what-have-you, который завершается вызовом gcc) в Makefile является тривиальным решением. Другие могут знать способы сделать это в Windows.

person jkerian    schedule 05.06.2009