在另一个类的构造函数的成员初始值设定项中声明的类是否可以在其外部显示?

P.W*_*P.W 17 c++ language-lawyer

考虑以下代码:

struct Foo { 
    void* p; 
    Foo() : p{(class Bar*)0} {}
};

Bar* bar;
Run Code Online (Sandbox Code Playgroud)

最新版本的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发出类似的消息.

godbolt上所有四个编译器的一致性查看.

那么哪个编译器按照标准是正确的?

eer*_*ika 13

  • [basic.lookup.elab] ...如果class-key引入了elaborated-type-specifier,并且这个查找没有找到先前声明的type-name ......则elaborated-type-specifier是一个引入的声明[basic.scope.pdecl]中描述的类名

  • [basic.scope.pdecl] - 用于表单的详细类型说明符

    类密钥标识符

    如果在命名空间作用域中定义的函数的decl-specifier-seq或parameter-declaration-clause中使用了elaborated-type-specifier [由于范围而不适用],否则,除了作为朋友声明之外, identifier在包含声明的最小命名空间或块范围中声明.

现在,棘手的一点.成员初始化列表是否"包含"在构造函数的范围内?如果没有,那么最小的块或命名空间范围是全局命名空间,程序将是格式良好的.如果是,则最小范围是构造函数的块范围,因此该类不会在全局范围内声明.

据我所知,没有规则说mem-init-list"包含在构造函数的块范围内".它位于大括号范围之外的大括号中.因此,该计划是良好的形式.

Mem-init-list 构造函数体[dcl.fct.def.general]的一部分,但该体既不是块作用域也不是命名空间作用域,因此它与[basic.scope.pdecl]中的规则无关. .

  • 我不知道"名称"和"评估"如何混合:查找名称并评估表达式,对吧? (3认同)

Whi*_*TiM 8

这是关于声明规则和范围规则; (详细说明的类型说明符也可以在表达式中声明类名).

scope.declarative/3:声明声明的名称被引入声明发生的范围,除了友元说明符的存在,elaborated-type-specifier的某些用法([dcl.type.elab])和using-directives([namespace.udir])改变了这种一般行为.

构造函数有一个范围(如eeroika的答案提及),同样在其中声明的任何内容.这就是为什么这应该是有效的

struct Foo { 
    void* p; 
    Foo() : p{(class Bar*)0} {
        Bar* bar;
    }
};
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/m3Tdle

但不是:

struct Foo { 
    void* p; 
    Foo() : p{(class Bar*)0} {
        //Scope of Bar only exists here
    }
};

Bar* bar;
Run Code Online (Sandbox Code Playgroud)

编辑: 关于这个的更多细节:

C++中所谓的"前向声明"在技术上是多种"形式"之一elaborated-type-specifier; 而且只有两个形式可能引入的名称.(强调我的)

详细说明的类型说明符中首先声明的类的声明点 如下:

  • (7.1)声明表格

    class-key attribute-specifier-seq opt identifier; //<-- Note the semi colon

    标识符在包含声明的作用域中声明为类名,否则

  • (7.2)表格的详细说明

    类密钥标识符

    如果阐述型说明符在使用DECL说明符-SEQ参数声明子句中的命名空间范围内定义的函数的,该标识符被声明为类名命名空间包含声明; 否则,除了作为友元声明之外,标识符在包含声明的最小命名空间或块范围中声明.[  注意:这些规则也适用于模板.-  尾注  ] [  注意: 其他形式的elaborated-type-specifier不声明新名称,因此必须引用现有的类型名称.参见[basic.lookup.elab]和[dcl.type.elab].-  结束说明  ]


这是另一个有趣的一点;

dcl.type.elab/1属性说明符-SEQ不应出现在详细阐述式说明符,除非后者是声明的唯一组分...

这就是为什么这个(下面)有效,请参见https://godbolt.org/z/IkmvGn ;

void foo(){
    void* n = (class Boo*)(0);
    Boo* b;
}

class [[deprecated("WTF")]] Mew;
Run Code Online (Sandbox Code Playgroud)

但是这个,(下面)是不正确的1 ; 在此处查看https://godbolt.org/z/8X1QKq ;

void foo(){
    void* n = (class [[deprecated("WTF")]] Boo*)(0);
}

class [[deprecated("WTF")]] Mew;
Run Code Online (Sandbox Code Playgroud)

要查看所有其他"形式",elaborated-type-specifier请参阅dcl.type.elab


编辑2

为了仔细检查我的理解,我进一步询问,并且@Simon Brand让我意识到了一些边缘情况,这部分是由basic.scope/declarative-4.note-2暗示的.但主要是通过这个答案中的第二个引用basic.scope/pdecl-7.2

函数参数范围内的详细类型说明符会将其类名泄露给封闭的命名空间(注意:这不是类范围).

这是有效的https://godbolt.org/z/Fx5B83:

struct Foo { 
    static void foo(void* = (class Bat*)0); //Leaks it past class-scope
};

void moo(){
    Bat* m;
}
Run Code Online (Sandbox Code Playgroud)

即使你将它隐藏在嵌套类https://godbolt.org/z/40Raup中:

struct Foo { 
    class Moo{
        class Mew{ 
            void foo(void* = (class Bat*)0); //
        };
    };
};

void moo(){
    Bat* m;
}
Run Code Online (Sandbox Code Playgroud)

...但是,该名称在最近的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;
}
Run Code Online (Sandbox Code Playgroud)

最后,

mem-init-list是一个复合语句,没有函数参数范围 ; 因此,如果要实现泄漏,请在函数参数范围内进行声明.请参阅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
Run Code Online (Sandbox Code Playgroud)