请考虑以下代码:
struct Foo {
void* p;
Foo() : p{(class Bar*)0} {}
};
Bar* bar;
最新版本的GCC(8.2)和Clang(7.0.0)无法编译。 ICC(19.0.1)也是如此。
但是MSVC(v19.16)会干净地编译它。
来自GCC的错误是:error: 'Bar' does not name a type; did you mean 'char'?
Clang和ICC发出类似的消息。
all four compilers at godbolt的符合性查看器。
那么按照标准,哪个编译器是正确的?
答案 0 :(得分:13)
[basic.lookup.elab] ...如果elaborated-type-specifier是由引入的 class-key,并且此查找找不到先前声明的type-name ... elaborated-type-specifier是一个声明,该声明引入了[basic.scope.pdecl]
中描述的class-name。 li>[basic.scope.pdecl]-用于形式的精细类型说明符
类键标识符
如果在a的decl-specifier-seq或parameter-declaration-clause中使用了详尽的类型说明符 在命名空间范围中定义的函数[由于范围不适用] ,...否则,除了作为朋友声明外,标识符在 包含声明的最小名称空间或块范围。
现在,棘手的地方。成员初始化程序列表“包含” 是否在构造函数的范围内?如果不是,则最小的块或名称空间范围是全局名称空间,并且程序格式正确。如果是,则最小范围是构造函数的块范围,因此不会在全局范围内声明该类。
据我所知,没有规则说mem-init-list“包含在构造函数的块范围内”。正是在大括号之外界定了范围。因此,该程序格式正确。
Mem-init-list 是构造函数主体[dcl.fct.def.general]的一部分,但该主体既不是块作用域也不是名称空间作用域,因此与[basic.scope.pdecl]中的规则。
答案 1 :(得分:8)
这是关于声明规则和范围规则的; (修饰的类型说明符还可在表达式中声明 class-name )。
scope.declarative/3: 由声明声明的名称将引入到 声明发生的地方,但有朋友在场 说明符,详尽类型说明符的某些用途 ([dcl.type.elab])和使用指令([namespace.udir])对此进行了更改 一般行为。
构造函数具有一个范围(如eeroika's answer所述),同样在其中声明了 。这就是为什么这应该是有效的
struct Foo {
void* p;
Foo() : p{(class Bar*)0} {
Bar* bar;
}
};
但不是:
struct Foo {
void* p;
Foo() : p{(class Bar*)0} {
//Scope of Bar only exists here
}
};
Bar* bar;
编辑: 有关此的更多详细信息:
从技术上讲,C ++中的“前向声明”是elaborated-type-specifier
的多种“形式”之一; only two表单可能会引入名称。 (强调我的)
首先在 精心设计的类型说明符如下:
(7.1)表示 form
的声明class-key attribute-specifier-seq opt 标识符;
//<-- Note the semi colon
该标识符在包含以下内容的范围中声明为类名 声明,否则
(7.2)表示 form
的详细类型说明符类密钥标识符
如果在定义的函数的 decl-specifier-seq 或 parameter-declaration-clause 中使用了修饰类型说明符在命名空间范围内,标识符在包含该声明的命名空间中声明为类名。否则,除作为朋友声明外,标识符在包含声明的最小名称空间或 block作用域中声明。 [注意:这些规则也适用于模板。 -尾注] [注: elaborated-type-specifier的其他 forms 不会声明新名称, 因此必须引用现有的类型名称。请参见 [basic.lookup.elab]和[dcl.type.elab]。 -尾注]
这是另一个有趣的地方;
dcl.type.elab/1一个 attribute-specifier-seq 不得出现在精心设计的类型说明符中,除非后者是 声明...
这就是为什么(下面)有效的原因,请在此处查看 https://godbolt.org/z/IkmvGn ;
void foo(){
void* n = (class Boo*)(0);
Boo* b;
}
class [[deprecated("WTF")]] Mew;
但这(下面)是错误的 1 ;在此处看到 https://godbolt.org/z/8X1QKq ;
void foo(){
void* n = (class [[deprecated("WTF")]] Boo*)(0);
}
class [[deprecated("WTF")]] Mew;
1 奇怪的是,GCC接受了它,但发出了以下警告:
attributes ignored on elaborated-type-specifier that is not a forward declaration [-Wattributes]
要查看 elaborated-type-specifier
的所有其他“形式”,请参见dcl.type.elab
编辑2
为再次确认我的理解,我进一步询问了@Simon Brand made me realise some edge cases,而basic.scope/declarative-4.note-2部分地暗示了这一点。但主要是在此答案basic.scope/pdecl-7.2
中用第二个引号引起来在 function-parameter-scope 中的精心设计的类型说明符将其类名泄漏到封闭的名称空间中(注意:这不是 class -scope )。
这是有效的 https://godbolt.org/z/Fx5B83 :
struct Foo {
static void foo(void* = (class Bat*)0); //Leaks it past class-scope
};
void moo(){
Bat* m;
}
即使您将其隐藏在嵌套类 https://godbolt.org/z/40Raup 中:
struct Foo {
class Moo{
class Mew{
void foo(void* = (class Bat*)0); //
};
};
};
void moo(){
Bat* m;
}
...但是,名称停止在最接近的namespace
https://godbolt.org/z/YDljDo 处泄漏。
namespace zoo {
struct Foo {
class Moo{
class Mew{
void foo(void* = (class Bat*)0);
};
};
};
}
void moo(){
zoo::Bat* m;
}
最后,
mem-init-list 是一种复合语句,没有 function-parameter-scope ;因此,如果要实现泄漏,请在 function-parameter-scope 中进行声明。参见 https://godbolt.org/z/CqejYS
struct Foo {
void* p;
Foo(void* = (class Zoo*)(0))
: p{(class Bar*)0} {
Bar* bar;
}
};
Zoo* m; //Zoo* leaked
//Bar* n //Bar* did not leak