Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Will a class that is declared within a member initializer of a constructor of another class be visible outside it?

Consider the following piece of code:

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

Bar* bar;

The latest versions of GCC (8.2) and Clang (7.0.0) fail to compile it. So does ICC (19.0.1).
However MSVC (v19.16) compiles it cleanly.

The error from GCC is: error: 'Bar' does not name a type; did you mean 'char'?
Clang and ICC issue similar messages.

Conformance viewer for all four compilers at godbolt.

So which of the compiler(s) is correct as per the standard?

like image 281
P.W Avatar asked Feb 12 '19 11:02

P.W


People also ask

What is mean by member initialisation list of constructor in C++?

Initializer List is used in initializing the data members of a class. The list of members to be initialized is indicated with constructor as a comma-separated list followed by a colon. Following is an example that uses the initializer list to initialize x and y of Point class.

Is constructor same as initializer?

Explicit initialization with constructors (C++ only)A class object with a constructor must be explicitly initialized or have a default constructor. Except for aggregate initialization, explicit initialization using a constructor is the only way to initialize non-static constant and reference class members.

How do you initialize a data member in a constructor?

Const member variables must be initialized. A member initialization list can also be used to initialize members that are classes. When variable b is constructed, the B(int) constructor is called with value 5. Before the body of the constructor executes, m_a is initialized, calling the A(int) constructor with value 4.

What is a member initializer?

Member initializer list is the place where non-default initialization of these objects can be specified. For bases and non-static data members that cannot be default-initialized, such as members of reference and const-qualified types, member initializers must be specified.


Video Answer


2 Answers

  • [basic.lookup.elab] ... If the elaborated-type-specifier is introduced by the class-key and this lookup does not find a previously declared type-name ... the elaborated-type-specifier is a declaration that introduces the class-name as described in [basic.scope.pdecl]

  • [basic.scope.pdecl] - for an elaborated-type-specifier of the form

    class-key identifier

    if the elaborated-type-specifier is used in the decl-specifier-seq or parameter-declaration-clause of a function defined in namespace scope [does not apply because of scope], ... otherwise, except as a friend declaration, the identifier is declared in the smallest namespace or block scope that contains the declaration.

Now, the tricky bit. Is the member initialiser list "contained" in the scope of the constructor or not? If not, then the smallest block or namespace scope is the global namespace and the program would be well-formed. If yes, then the smallest scope is the block scope of the constructor, and thus the class would not be declared in the global scope.

As far as I can tell, there is no rule saying that mem-init-list is "contained in the block scope of the constructor". It is outside the curly brackets that delimit the scope. As such, the program is well-formed.

Mem-init-list is part of the constructor body [dcl.fct.def.general], but that body is neither a block scope nor a namespace scope, so it is not relevant to the rule in [basic.scope.pdecl].

like image 74
eerorika Avatar answered Oct 16 '22 13:10

eerorika


This is about declaration rules and scope rules; (elaborated type specifier may also declare a class-name in an expression).

scope.declarative/3: The names declared by a declaration are introduced into the scope in which the declaration occurs, except that the presence of a friend specifier, certain uses of the elaborated-type-specifier ([dcl.type.elab]), and using-directives ([namespace.udir]) alter this general behavior.

a constructor has a scope (as eeroika's answer mentions), likewise anything declared therein. That is why this should be valid

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

https://godbolt.org/z/m3Tdle

But not:

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

Bar* bar;

EDIT: More details about this:

The so-called "forward declaration" in C++ is technically one of multiple "forms" of elaborated-type-specifier; And only two forms may introduce a name. (Emphasis mine)

The point of declaration of a class first declared in an elaborated-type-specifier is as follows:

  • (7.1) for a declaration of the form

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

    the identifier is declared to be a class-name in the scope that contains the declaration, otherwise

  • (7.2) for an elaborated-type-specifier of the form

    class-key identifier

    if the elaborated-type-specifier is used in the decl-specifier-seq or parameter-declaration-clause of a function defined in namespace scope, the identifier is declared as a class-name in the namespace that contains the declaration; otherwise, except as a friend declaration, the identifier is declared in the smallest namespace or block scope that contains the declaration. [ Note: These rules also apply within templates. —- end note ] [ Note: Other forms of elaborated-type-specifier do not declare a new name, and therefore must refer to an existing type-name. See [basic.lookup.elab] and [dcl.type.elab]. —- end note ]


Here's another interesting bit about it;

dcl.type.elab/1 An attribute-specifier-seq shall not appear in an elaborated-type-specifier unless the latter is the sole constituent of a declaration...

That is why this (below) is valid, See it here https://godbolt.org/z/IkmvGn;

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

class [[deprecated("WTF")]] Mew;

But this, (below) is incorrect1; See it here https://godbolt.org/z/8X1QKq;

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

class [[deprecated("WTF")]] Mew;
  • 1Strangely, GCC accepts it, but gives this warning:

    attributes ignored on elaborated-type-specifier that is not a forward declaration [-Wattributes]
    

To see all the other "forms" of elaborated-type-specifier see dcl.type.elab


Edit 2

To double-check my understanding, I asked further, and @Simon Brand made me realise some edge cases, and that is partly hinted by basic.scope/declarative-4.note-2. But chiefly by the second quote in this answer basic.scope/pdecl-7.2

An elaborated-type-specifier within a function-parameter-scope will leak its class-name to the enclosing namespace (note: this is not class-scope).

This is valid https://godbolt.org/z/Fx5B83:

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

void moo(){
    Bat* m;
}

Even if you hide it in nested classes https://godbolt.org/z/40Raup:

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

void moo(){
    Bat* m;
}

... However, the name stops leaking at the closest 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;
}

Finally,

mem-init-list is a compound-statement and does not have a function-parameter-scope; So, if you want to achieve, the leakage, do the declaration in the function-parameter-scope. See 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
like image 23
WhiZTiM Avatar answered Oct 16 '22 15:10

WhiZTiM