我正在开发 C++ 的源代码编辑器并提出了简单的优化,我不需要在当前编辑的语句之前(基本上,在前一个分号/结束之前)使代码无效(例如突出显示、重建 AST、进行静态分析)大括号),但我不确定 C++ 是否总是如此。
例如,在 Java 中,可以在编辑位置之后调用声明/定义的函数。因此,如果用户向函数添加参数,则错误标记应放置在编辑位置之前的代码中。在 C++ 中,函数应在使用前声明(如果声明与定义不匹配,则错误将出现在定义中)。
在类中内联定义的成员函数体将在概念上(实际上,在我知道的编译器中)在类的末尾进行解析,因此可以访问在它们之后声明的类的成员。
通常,在模板定义之外,C++ 中名称查找的规则要求在使用名称之前声明一个名称。因此,您的想法在大多数情况下都有效。不幸的是——正如 SebastianRedl 所指出的——有一个特殊情况,这个经验法则不适用。
在类定义中,类的所有成员(及其封闭类)的声明在成员函数主体(包括 ctor-initializer-list 或异常规范)内的名称查找期间可见,或者在成员函数的默认参数。
插图:
struct A
{
struct B
{
static void f(int i = M()) // uses 'A::M' declared later
{
A::f(); // calls A::f(int) declared later
}
};
static void f(void*)
{
f(); // calls A::f(int) declared later
}
static void f(int i = M()) // uses 'M' declared later
{
}
typedef int M;
};
如果被修改的标记出现在成员函数体或默认参数中,则必须重新解析包含该标记的所有类。
来自 C++ 工作草案标准 N3337:
3.4.1 非限定名称查找 [basic.lookup.unqual]
用于定义 X 类的成员函数 (9.3) 后跟在函数的 declarator-id 或类 X 的非静态数据成员 (9.2) 的大括号或相等初始化器中的名称应在一个中声明以下几种方式:
— 在其用于使用它的块或封闭块(6.3)之前,或
— 应为 X 类的成员或 X (10.2) 的基类的成员,或
— 如果 X 是 Y 类 (9.7) 的嵌套类,应是 Y 的成员,或应是 Y 的基类的成员(此查找依次适用于 Y 的封闭类,从最里面的封闭类开始) , 或者
— 如果 X 是局部类 (9.8) 或者是局部类的嵌套类,则在包含 X 类定义的块中的 X 类定义之前,或
— 如果 X 是命名空间 N 的成员,或者是作为 N 成员的类的嵌套类,或者是局部类或作为 N 成员的函数的局部类内的嵌套类,则在在命名空间 N 或 N 的封闭命名空间之一中使用名称。
模板分两个阶段编译。第一阶段是定义它们的地方,第二阶段是实例化它们的地方。当前的编译器可能会在实例化时将模板错误归咎于您,当替换实际参数会导致模板中出现错误时。
您的编辑遵循相同的逻辑是完全合理的。如果您看到模板定义看起来不错,那么它通过了第一阶段。当正在编辑的代码实例化模板时,重新检查并将第二阶段中的任何错误归咎于实例化。
这暗示了一个更根本的问题。你说哪里出错了?C++ 标准不在乎。就它而言,“某处的语法错误”是一个足够的诊断。因此,如果有一个内联方法试图访问一个不存在的成员this->a
,您可以声明该方法存在错误。但同样有效,您可以在最后};
声明该类未能定义必要的成员a
。
所有这些错误的根本原因是两段代码必须在某事上达成一致。如果他们不这样做,您可以选择责备谁。对于您的编辑,您可以责怪最后出现的片段。这只是使诊断措辞正确的问题。