C ++:使用编译器API而不是C ++功能进行元编程


10

这最初是一个SO问题,但我意识到这是非常不合常规的,并且根据网站上的实际描述,它可能更适合程序员。因为该问题在概念上具有很大的分量。

我一直在学习clang LibTooling,它是一个非常强大的工具,能够以友好的方式(即以语义的方式,而不是通过猜测)公开代码的整个“坚韧不拔” 。如果clang可以编译您的代码,则clang 可以确定该代码内每个字符的语义。

现在让我退一步。

当人们从事C ++模板元编程时(特别是当冒险超越模板进入聪明的领域,尽管使人恐惧)时,会出现许多实际问题。老实说,对于包括我自己在内的许多程序员而言,模板的许多常规用法也有些令人恐惧。

我想一个很好的例子就是编译时字符串。这是一个已经存在一年多的问题了,但是很显然,到目前为止,C ++对于普通人来说并不容易。尽管看这些选项还不足以引起我的恶心,但我仍然对能够产生神奇的,最高效率的机器代码以适应我的软件中任何花哨的应用程序没有信心。

我的意思是,让我们面对现实吧,伙计们,琴弦非常简单和基本。我们中的某些人只是想要一种便捷的方法来发出带有某些字符串的机器代码,而这些字符串在某些情况下“嵌入”了比直接编写代码时要多得多。在我们的C ++代码中。

输入clang和LibTooling,这将公开源代码的抽象语法树(AST),并允许简单的自定义C ++应用程序正确正确地操作原始源代码(使用Rewriter)以及AST中所有对象的丰富的面向对象的语义模型。它处理很多事情。它了解宏扩展,并让您遵循这些链。是的,我说的是源代码到源代码的转换或翻译。

我在这里的基本论点是,使用clang,现在我们可以创建可执行文件,这些可执行文件本身可以用作C ++软件的理想自定义预处理程序阶段,并且可以使用C ++实现这些元编程阶段。我们仅受以下事实的约束:该阶段必须接受有效的C ++代码的输入,并产生更多有效的C ++代码作为输出。加上您的构建系统适用的任何其他限制。

输入至少必须非常接近有效的C ++代码,因为毕竟clang是编译器的前端,而我们只是在四处寻找并利用其API发挥创意。我不知道是否有任何规定可以定义要使用的新语法,但是显然我们必须开发一种方法来正确解析它并将其添加到clang项目中。期望更多的是在clang项目中超出范围。

没问题 我可以想象一些无操作宏函数可以处理此任务。

查看我正在描述的另一种方法是通过运行我们的源代码的AST(感谢clang及其API)来使用运行时C ++实现元编程构造,而不是使用语言本身可用的更有限的工具来实现它们。这也具有明显的编译性能优势(繁重的模板头与您使用它们的频率成比例地减慢了编译速度。然后,许多已编译的东西被链接器仔细地匹配并丢弃了)。

但是,这样做的代价是在构建过程中引入了额外的一两个步骤,并且还要求(当然)编写一些更详细的软件(但至少是简单的运行时C ++)作为我们工具的一部分。

那不是全部。我非常确定,生成核心语言功能极其困难或不可能的代码可以提供更大的功能空间。在C ++中,您可以编写模板,宏或两者的疯狂组合,但是在clang工具中,您可以在运行时以C ++可以实现的任何方式修改类和函数,同时可以完全访问语义内容,除了模板和宏以及其他所有内容。

因此,我想知道为什么每个人都还没有这样做。是不是来自clang的此功能如此新,并且没人熟悉clang的AST的巨大类层次结构?那不可能。

也许我只是低估了这一点的难度,但是使用clang工具执行“编译时字符串操作”几乎在犯罪上很简单。它很冗长,但是非常简单。所需要的只是一堆无操作宏函数,它们映射到实际的实际std::string操作。clang插件通过获取所有相关的无操作宏调用来实现此目的,并使用字符串执行操作。然后将该工具作为构建过程的一部分插入。在构建过程中,这些无操作宏函数调用会自动评估为其结果,然后作为纯旧的编译时字符串插入到程序中。然后可以照常编译该程序。实际上,结果是该程序的移植性也大大提高,不需要新的支持C ++ 11的编译器。


这是一个非常长的问题。您能否将其浓缩为最相关的一点?
阿蒙2014年

我确实发布了很多很长的问题。但我认为,特别是对于这一部分,问题的所有部分都很重要。也许跳过前6段?哈哈。
史蒂文·卢

3
听起来很像Lisp最早的语法宏,最近被Haxe,Nemerle,Scala和类似语言所采用。关于为什么将Lisp宏视为有害的文章有很多读物。尽管我还没有听到有说服力的论点,但是您可能会找到为什么人们不愿将它们添加到每种语言中的原因(事实并非一定如此)。
back2dos

是的,它是元化C ++。这可能意味着更好,更快的代码。至于那些语言。好吧,我应该从哪里开始。用这些语言中的任何一种实现的价值数百万美元的视频游戏是什么?用这些语言中的任何一种实现的现代Web浏览器是什么?OS内核?好吧,实际上Haxe似乎具有一定的吸引力,但是您明白了。
史蒂文·卢

1
@nwp,嗯,我不禁指出,您似乎错过了整个帖子的重点。编译时字符串只是我们现在可以使用的功能的最人为设计和最小的具体示例。
史蒂文·卢

Answers:


7

是的,弗吉尼亚,有一个圣诞老人。

使用程序来修改程序的概念已经存在了很长时间。最初的想法来自约翰·冯·诺伊曼John von Neumann),它以存储程序计算机的形式出现。但是,以任意方式修改机器代码的机器代码非常不方便。

人们通常希望修改代码。这主要以程序转换系统(PTS)的形式实现。

PTS通常为至少一种编程语言提供解析为AST,操作该AST以及重新生成有效源文本的功能。如果实际上您在大多数主流语言中进行挖掘,有人已经构建了这样的工具(Clang是C ++的示例,Java编译器提供了此功能作为API,Microsoft提供了Rosyln,Eclipse的JDT等),并且提供了程序实际上非常有用的API。对于更广泛的社区而言,几乎每个特定于语言的社区都可以指向具有不同成熟度级别(通常适度,许多“仅生成AST的解析器”)实现的对象。快乐的元编程。

[有一个面向反射的社区,尝试从编程语言内部进行元编程,但仅实现“​​运行时”行为修改,并且仅在语言编译器可以通过反射提供某些信息的范围内。除了LISP之外,总是存在有关该程序的详细信息,这些细节不能通过反射得到(“ Luke,您需要源代码”),这总是限制了反射的作用。]

更有趣的PTS可以对任意语言执行此操作(您可以为工具提供语言说明作为配置参数,至少包括BNF)。这种PTS还允许您执行“源到源”转换,例如,直接使用目标语言的表面语法指定模式;使用这样的模式,您可以编码感兴趣的片段,和/或找到并替换代码片段。这比编程API更加方便,因为您不必了解有关AST的每一个微观细节即可完成大部分工作。认为这是元元编程:-}

缺点:除非PTS提供各种有用的静态分析(符号表,控制和数据流分析),否则很难以这种方式编写真正有趣的转换,因为您需要检查类型并验证大多数实际任务的信息流。不幸的是,这种功能实际上在一般的PTS中很少见。(对于以前提出的“如果我刚刚有一个解析器...”,总是无法找到它。)

有一个定理说,如果您可以进行字符串重写(因此进行树重写),则可以进行任意转换;因此,许多PTS都依赖于此,声称您可以仅通过它们提供的树重写来对任何程序进行元编程。尽管该定理在某种意义上满足了您现在确定可以做的一切,但它并不令人满意,就如同图灵机执行任何操作的能力并不能使对图灵机进行编程成为选择的方法一样。(对于仅具有过程性API的系统,这同样适用,如果它们可以让您对AST进行任意更改,(实际上,我认为Clang并非如此))

您想要的是两全其美的系统,它为您提供了语言参数化的PTS类型的通用性(甚至处理多种语言),并带有附加的静态分析功能,能够将源到源转换与过程混合在一起蜜蜂。我只知道有两个这样做:

  • Rascal(MPL)元编程语言
  • 我们的DMS软件再造工具包

除非想自己编写语言描述和静态分析器(对于C ++而言,这是一项艰巨的工作,这就是为什么Clang既被构造为编译器又是通用过程元编程基础的原因),否则您将需要具有成熟语言描述的PTS已经可用。否则,您将花费所有时间来配置PTS,而没有人去做您真正想做的工作。[如果您选择一种随机的非主流语言,则很难避免此步骤]。

Rascal试图通过选择“ OPP”(其他人的解析器)来做到这一点,但这对静态分析部分没有帮助。我认为他们手中的Java很好,但是我很确定他们不会使用C或C ++。但是,它是一种学术研究工具;很难责怪他们。

我强调,我们的 [商业] DMS工具确实具有可用的Java,C,C ++完整前端。对于C ++,它几乎涵盖了C ++ 14中用于GCC的所有内容,甚至包括Microsoft的变体(并且我们现在正在完善),宏扩展和条件管理以及方法级控制和数据流分析。是的,您可以以实际的方式指定语法更改。我们为客户构建了定制的VectorC ++系统,该系统从根本上扩展了C ++以使用相当于F90 / APL数据并行数组操作的数量。DMS已用于在大型C ++系统上执行其他大量的元编程任务(例如,应用程序体系结构调整)。(我是DMS的架构师)。

快乐的元元编程。


很酷,我认为Clang和DMS虽然有一些重叠的功能,但是它们并不是真正属于同一类别的软件。我的意思是,一个可能非常昂贵,我可能永远也无法证明访问它所需的资源是合理的,另一个是无限制的免费开源。这是一个巨大的差异...这些令人兴奋的元编程功能让我感到兴奋的部分事实是,不仅可以自由使用它,而且可以自由分发基于clang的二进制工具是允许的。
史蒂文·卢

与免费销售相比,任何以商业方式出售的产品都“极其昂贵”。原始成本不是问题;重要的是,对于某些人来说,购买商业产品的回报要高于免费人工制品的回报,否则就不会有商业软件。这显然取决于您的特定需求。Clang是工具领域中一个有趣的方面,并且肯定会有一些有用的应用程序。我想(由于我是DMS架构师)认为DMS具有更广泛的功能。例如,Clang不太可能很好地支持C ++以外的语言。
Ira Baxter

当然。毫无疑问,DMS的功能非常强大,几乎达到了魔术的水平(la Arthur C. Clarke),而clang很棒,但它实际上只是一个编写良好的C ++前端,其中有很多。向前迈出了许多小步,将其与DMS进行比较仍然不公平。las,即使拥有如此强大的工具供您使用,可运行的软件也无法自行编写。它必须通过使用工具进行仔细的翻译,或者(几乎总是使用高级选项)重新编写而成。
史蒂文·卢

您负担不起从头开始构建Clang或DMS之类的工具。通常,您也负担不起与10个人组成的团队一起编写5年以上的应用程序。随着软件规模和使用寿命的不断增长,我们将越来越需要这样的工具。
Ira Baxter 2014年

@StevenLu:嗯,DMS感谢您的赞美,但这没什么神奇的。DMS确实受益于近2个线性十年的工程以及一个运行良好的干净的架构平台(aw,shucks,YMMV)。同样,Clang包含许多出色的工程设计。我同意,它们并不是为解决完全相同的问题而设计的……DMS的范围明确地打算在进行符号程序操纵时更大,而在成为生产编译器时则要小得多。
Ira Baxter 2014年

4

使用编译器的API(而不是使用模板)在C ++中进行元编程确实很有趣并且切实可行。由于元编程尚未(尚未)标准化,因此您将被绑定到特定的编译器,而模板则不是这种情况。

因此,我想知道为什么每个人都还没有这样做。是不是来自clang的此功能是如此新颖,并且没人熟悉clang的AST的巨大类层次结构?那不可能。

许多人这样做,但使用其他语言。我的观点是,大多数C ++(或Java或C)开发人员都不认为(可能是正确的)需求,或者不习惯元编程方法。我还认为他们对自己的IDE的重构/代码生成功能感到满意,并且任何幻想者都可能被认为过于复杂/难以维护/难以调试。没有适当的工具,这可能是正确的。您还应该考虑惯性和其他非技术性问题,例如雇用和/或培训人员。

顺便说一句,由于我们提到的是Common Lisp及其宏系统(请参阅Basile的回答),所以我必须说,就在昨天,Clasp发行了(我不隶属于):

Clasp打算成为编译为LLVM IR的兼容Common Lisp实现。而且,它向开发人员公开了Clang库(AST,Matcher)。

  • 首先,这意味着您可以用CL编写并且不再使用C ++,除非使用其库(并且如果需要宏,请使用CL宏)。

  • 其次,您可以使用CL为现有的C ++代码编写工具(分析,重构等)。


3

一些C ++编译器具有或多或少有文档记录且稳定的API,尤其是大多数自由软件编译器。

Clang / LLVM主要是大量的库,您可以使用它们。

最近的GCC正在接受插件。特别是,您可以使用MELT(它本身是一个元插件,为您提供高级域特定语言来扩展GCC)对其进行扩展。

请注意,C ++ 的语法在GCC中不容易扩展(在Clang中也可能不容易),但是您可以添加自己的编译指示,内建函数,属性和编译器传递来完成您想做的事情(也许还提供了一些调用这些事情的预处理器宏)给出用户友好的语法)。

您可能对多级语言和编译器感兴趣,请参阅C.Calcagno等人的《使用AST,Gensym和反射实现多级语言》。并解决MetaOcaml的问题。您当然应该查看Common Lisp的宏设施。您可能会对JIT库(例如libjitGNU lightning甚至LLVM)感兴趣,或者只是-在运行时!-生成一些C ++代码,将其编译到共享的对象动态库中,然后将dlopen(3)共享宾语。J.Pitrat的博客也与这种反思性方法有关。还有RefPerSys


有趣。非常高兴看到海湾合作委员会在这里继续发展。这不是解决我所问问题的答案,但是我还是喜欢它。
史蒂文·卢

回复:您的新修改...这是关于代码重写本身的一个好方法。实际上,这确实也开始将这种元程序功能带入C ++,比以前更易于访问,这也很有趣。
史蒂文·卢
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.