C++ 代码生成

发布于 2024-07-23 11:04:41 字数 1782 浏览 5 评论 0原文

在我让 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 宏定义上生成类。

我看不出解决这个问题的方法,但想知道是否有人对 C++ 有更深入的了解。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

情愿 2024-07-30 11:04:41

如果您正在寻找一种使用 C++ 代码生成来序列化/反序列化数据的方法,我会看看 Google protobufs ( http://code.google.com/p/protobuf/)或 Facebook 的 Thrift (http:// /incubator.apache.org/thrift/)。

对于 protobuf,您可以像这样编写数据定义:

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等。

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.

厌味 2024-07-30 11:04:41

您也许可以使用 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 ());
}

以下类为 Record 实现 getter 成员。 使用 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;
}    

由于这都是模板代码,因此您应该能够将记录嵌套在记录中。

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.

多彩岁月 2024-07-30 11:04:41

这是我在 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。

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.

空宴 2024-07-30 11:04:41

我可能会尝试使用记录 mixin 来做类似的事情——在编译时自动向类添加功能

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

   }

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

   }
醉南桥 2024-07-30 11:04:41

一般来说,如果将所有内容合并到一个宏中,然后利用 Boost Preprocessor 库来定义您的类,您就可以准确地完成您想要的任务。 看看我如何实现 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

In general you can accomplish exactly what you want if you merge everything into one macro and then leverage Booost Preprocessor library to define your class. Look at how I implemented the MACE_REFLECT macro which does a partial specialization of an entire class and must reference each name twice in different parts.

This is very similar to how I automatically parse JSON into structs with the help of the pre-processor.

Given your example, I would translate it as such:

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

I can now 'visit' the members of Name from my parser:

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

If your objects can be represented as structs, arrays, key-value-pairs and primitives, then this technique works wonders and gives me instant serialization/deserializtion to/from json/xml or your custom record format.

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

拥抱我好吗 2024-07-30 11:04:41

在某些情况下,我不太确定您在寻找什么。

  • 规范中的 foo 和 bar 会发生什么情况?
  • getGroupName 实际上返回什么? (富,酒吧)? 或组名?

看起来您正在尝试创建一种用于加载和访问任意布局的磁盘结构的机制。 这准确吗? (编辑:刚刚注意到“set”成员函数......所以我猜你正在寻找完整的序列化)

如果你在 *nix 系统上,指定你自己的编译器编译为 .o (可能是 perl/python /what-have-you 脚本在 Makefile 中以调用 gcc 结束)是一个简单的解决方案。 其他人可能知道在 Windows 上执行此操作的方法。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文