在另一个类的构造函数的成员初始化器中声明的类在外部是否可见?

时间:2019-02-12 11:59:24

标签: c++ language-lawyer

请考虑以下代码:

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的符合性查看器。

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

2 个答案:

答案 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;
    }
};

https://godbolt.org/z/m3Tdle

但不是:

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