用于C++的分析器如何区分比较和模板实例化

How does a parser for C++ differentiate between comparisons and template instantiations?

本文关键字:比较 实例化 何区 C++ 分析器 用于      更新时间:2023-10-16

在C++中,符号"<"和">"用于比较以及表示模板参数。因此,代码片段

[...] Foo < Bar > [...]

可以解释为以下两种方式中的任何一种:

  • 带有模板参数栏的 Foo 类型的对象
  • 将Foo与Bar进行比较,然后将结果与接下来的结果进行比较

C++编译器的解析器如何有效地在这两种可能性之间做出决定?

如果已知Foo是模板名称(例如,template <...> Foo ...声明在范围内,或者编译器看到template Foo序列(,则Foo < Bar不能是比较。它必须是模板实例化的开始(或本周调用的任何Foo < Bar >(。

如果Foo不是模板名称,则Foo < Bar是比较。

在大多数情况下,知道Foo是什么,因为标识符通常必须在使用前声明,因此决定一种或另一种方式没有问题。但有一个例外:解析模板代码。如果Foo<Bar>在模板内,并且Foo的含义取决于模板参数,则不知道Foo是否是模板。语言标准指示将其视为非模板,除非前面有关键字 template

解析器可以通过将上下文反馈回词法分析器来实现这一点。词法分析器将Foo识别为不同类型的标记,具体取决于分析器提供的上下文。

要记住的重要一点是,C++语法不是上下文无关的。 即,当解析器看到Foo < Bar(在大多数情况下(知道Foo引用模板定义(通过在符号表中查找它(,因此<不能进行比较。

在困难的情况下,您必须指导解析器。例如,假设 正在使用模板成员函数编写类模板,您希望显式专用该函数。您可能需要使用如下语法:

 a->template foo<int>();

(在某些情况下;有关详细信息,请参阅在模板类中调用模板函数(

此外,非类型模板参数内的比较必须用括号括起来,即:

foo<(A > B)>

foo<A > B>

非静态数据成员初始值设定项带来更多乐趣: http://open-std.org/JTC1/SC22/WG21/docs/cwg_active.html#325

C 和 C++ 解析器是"上下文相关的",换句话说,对于给定的标记或词法,它不能保证是不同的并且只有一个含义 - 这取决于使用令牌的上下文。

因此,编译器的解析器部分将知道(通过了解"它在源代码中的位置"(它正在解析某种类型或某种比较(这并不容易知道,这就是为什么阅读有能力的 C 或 C++ 编译器的源代码并不完全直截了当 - 有很多条件和函数调用检查"这是其中之一, 如果是这样,请执行此操作,否则请执行其他操作"(。

关键字 template 帮助编译器了解正在发生的事情,但在大多数情况下,编译器只是知道,因为<在其他方面没有意义 - 如果它在任何一种形式中都没有意义,那么这是一个错误,所以这只是一个试图弄清楚程序员可能想要什么的问题 - 这是有时的原因之一, 一个简单的错误,如缺少}template,可能会导致整个解析误入歧途,并导致成百上千个错误[尽管理智的编译器在合理的数字后停止,以免用错误消息填充整个宇宙]

这里的大多数答案都混淆了确定符号的含义(我称之为"名称解析"(和解析(狭义地定义为"可以读取程序的语法"(。

您可以单独执行这些任务。

这意味着你可以为C++构建一个完全独立于上下文的解析器(就像我的公司Semantic Designs所做的那样(,并将决定符号含义的问题留给一个明确独立的后续任务。

现在,该任务由源代码的可能的语法解释驱动。 在我们的解析器中,这些在解析中被捕获为歧义

名称解析

的作用是收集有关名称声明的信息,并使用该信息来确定哪些不明确的解析没有意义,然后简单地删除它们。 剩下的是一个有效的解析,有一个有效的解释。

在实践中完成名称解析的机制是一团糟。 但这是C++委员会的错,而不是解析器或名称解析器。使用我们的工具消除歧义实际上是自动完成的,使该部分实际上非常好,但如果您不查看我们的工具,您不会欣赏这一点,但我们这样做是因为这意味着一个小型工程团队能够构建它。

查看模板与小于模板的解析示例,而不是我们的解析器完成的最令人烦恼的解析C++。