为结构/类自动生成流运算符

Auto-generate stream operator for struct/class

本文关键字:运算符 自动生成 结构      更新时间:2023-10-16

是否有工具可以为结构或类自动生成 ostream <<运算符?

输入(取自一个调试-打印函数以规则所有(:

typedef struct ReqCntrlT    /* Request control record */
{
  int             connectionID;
  int             dbApplID;
  char            appDescr[MAX_APPDSCR];
  int             reqID;
  int         resubmitFlag;
  unsigned int    resubmitNo;
  char            VCIver[MAX_VCIVER];
  int             loginID;
}   ReqCntrlT;

输出:

std::ostream& operator <<(std::ostream& os, const ReqCntrlT& r) 
{
   os << "reqControl { "
      << "ntconnectionID: " << r.connectionID 
      << "ntdbApplID: " << r.dbApplID 
      << "ntappDescr: " << r.appDescr
      << "ntreqID: " << r.reqID
      << "ntresubmitFlag: " << r.resubmitFlag
      << "ntresubmitNo: " << r.resubmitNo
      << "ntVCIver: " << r.VCIver
      << "ntloginID: " << r.loginID
      << "n}";
   return os; 
}

任何工具都可以,Python/Ruby脚本将是首选。

这需要的是一个工具,可以准确地解析C++,枚举各种类/结构,确定并生成每个类/结构的"序列化",然后将生成的代码停放在"正确的位置"(大概与找到结构的范围相同(。 它需要一个完整的预处理器来处理实际代码中指令的扩展。

我们的DMS软件再造工具包及其C++11前端可以做到这一点。 DMS 通过提供通用解析/AST 构建、符号表构建、流程和自定义分析、转换和源代码重新生成功能,支持自定义工具的构建。 C++前端使 DMS 能够解析C++并构建准确的符号表,以及将修改后的或新的 AST 漂亮地打印回可编译的源形式。DMS及其C++前端已被用于对C++代码进行大规模转换。

您必须向 DMS 解释您要做什么;枚举符号表条目、询问是否为结构/类类型声明、确定声明的范围(记录在符号表条目中(、通过组合图面语法模式构造 AST,然后应用转换以插入构造的 AST 似乎很简单。

所需的核心图面语法模式是槽和函数体的语法模式:

 pattern ostream_on_slot(i:IDENTIFIER):expression =
   " << "nt" << tostring(i) << r.i "; -- tostring is a function that generates "<name>"
 pattern ostream_on_struct(i:IDENTIFIER,ostream_on_slots:expression): declaration =
   " std::ostream& operator <<(std::ostream& os, const i& r) 
     { os << tostring(i) << " { " << ostream_on_slots << "n}";
       return os; 
     }

必须为ostream_on_slot组成单个树:

 pattern compound_ostream(e1:expression, e2:expression): expression
     = " e1 << e2 ";
使用这些模式,可以直接枚举结构的

插槽,构造主体的 ostream,并将其插入到结构的整体函数中。

有两种

主要方法可以做到这一点:

  • 使用外部解析工具(例如挂接到 Clang 绑定的 Python 脚本(
  • 使用元编程技术

.. 当然,它们可以混合。

我对 Clang Python 绑定没有足够的知识来回答使用它们,所以我将专注于元映射。


基本上,你所要求的需要反省。C++不支持完全内省,但是使用元编程技巧(和模板匹配(,它可以在编译时支持有限的内省技术子集,这足以满足我们的目的。

为了轻松地混合元编程和运行时操作,更容易使用库:Boost.Fusion。

如果你调整你的结构,使其属性被描述为一个 Boost.Fusion 序列,那么你就可以自动对序列应用大量的算法。在这里,关联序列是最好的。

因为我们谈论的是元编程,所以映射将类型与类型相关联。

然后,您可以使用 for_each 循环访问该序列。


我会掩盖细节,仅仅是因为已经有一段时间了,我不记得所涉及的语法,但基本上这个想法是:

// Can be created using Boost.Preprocessor, but makes array types a tad difficult
DECL_ATTRIBUTES((connectionId, int)
                (dbApplId, int)
                (appDescr, AppDescrType)
                ...
                );

这是声明融合映射及其相关标签的语法糖:

struct connectionIdTag {};
struct dbApplIdTag {};
typedef boost::fusion::map<
    std::pair<connectionIdTag, int>,
    std::pair<dbApplIdTag, int>,
    ...
    > AttributesType;
AttributesType _attributes;

然后,可以简单地使用以下方法构建需要对属性应用的任何操作:

// 1. A predicate:
struct Predicate {
    template <typename T, typename U>
    void operator()(std::pair<T, U> const&) const { ... }
};
// 2. The for_each function
for_each(_attributes, Predicate());

要实现这一点,唯一的方法是使用在源文件上运行的外部工具。

首先,您可以使用 c/c++ 分析工具,并使用它来从源代码中检索解析树。然后,一旦你有了解析树,你只需要搜索结构。对于每个结构,您现在可以生成序列化结构字段的operator<<重载。您还可以生成反序列化运算符。

但这取决于你有多少个结构:对于一打,最好是手动编写运算符,但如果你有几百个结构,你可能想要编写(反(序列化运算符生成器。

我确实以两种方式理解了你的问题。

如果你想生成程序的自动状态报告,我建议你检查Boost.Serialization。但是,它不会在编译时生成代码作为第一步或获得灵感。下面的代码将帮助您生成之后可以读取的 xml 或 txt 文件。

typedef struct ReqCntrlT    /* Request control record */
{
  int             connectionID;
  int             dbApplID;
  char            appDescr[MAX_APPDSCR];
  int             reqID;
  int         resubmitFlag;
  unsigned int    resubmitNo;
  char            VCIver[MAX_VCIVER];
  int             loginID;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & connectionID;
        ar & reqID;
        ...
    }
}   ReqCntrlT;

有关更多详细信息,请参阅教程:http://www.boost.org/doc/libs/1_49_0/libs/serialization/doc/index.html

如果您只是尝试通过仅提供参数名称来"编写"代码。然后你应该看看 python 或 perl 中的正则表达式。此解决方案的主要默认值是您处于结构的"离线"状态,即每次更改某些内容时都必须运行它。

伯努瓦。

您可以使用 LibClang 解析源代码并生成 ostream 运算符:

# © 2020 Erik Rigtorp <erik@rigtorp.se>
# SPDX-License-Identifier: CC0-1.0
import sys
from clang.cindex import *
idx = Index.create()
tu = idx.parse(sys.argv[1], ['-std=c++11'])
for n in tu.cursor.walk_preorder():
    if n.kind == CursorKind.ENUM_DECL:
        print(
            f'std::ostream &operator<<(std::ostream &os, {n.spelling} v) {{n  switch(v) {{')
        for i in n.get_children():
            print('    case {type}::{value}: os << "{value}"; break;'.format(
                type=n.type.spelling, value=i.spelling))
        print('  }n  return os;n}')
    elif n.kind == CursorKind.STRUCT_DECL:
        print(
            f'std::ostream &operator<<(std::ostream &os, const {n.spelling} &v) {{')
        for i, m in enumerate(n.get_children()):
            print(
                f'  os << "{", " if i != 0 else ""}{m.spelling}=" << v.{m.spelling};')
        print('  return os;n}')

来自我的文章: https://rigtorp.se/generating-ostream-operator/