StoryTeller StoryTeller - 3 months ago 15
C++ Question

Declare inner class name as part of another declaration

While attempting to define a class by the pimpl idiom, I tried to save a line of code by rolling the class forward declaration into the pimpl definition.

class A
{
public:
class Impl *pimpl;
};


The above declaration compiles fine. What doesn't work, is the attempt to define
A::Impl
. As the program bellow demonstrates:

class A
{
public:
class Impl *pimpl;
};

class A::Impl
{

};

int main() {
// your code goes here
return 0;
}


It results with:


error: qualified name does not name a class before '{' token


Now, one can introduce a new class name, by an elaborate type specifier, as part of another declaration. But it is in fact
Class ::Impl
which is introduced.

class A
{
public:
class Impl *pimpl;
};

class Impl
{

};

int main() {
Impl i;
A a;
a.pimpl = &i;
return 0;
}


Why is it that a forward declaration on it's own line (as the idiom is often used), will introduce
A::Impl
, but when declared as part of the pointer definition, will introduce
::Impl
?

Update

I'm not asking how to make the idiom work. My question is why the single line

class Impl *pimpl;


doesn't have the same effect as the two lines

class Impl;
Impl *pimpl;


In the context of a class definition.

I'm baffled because the name becomes properly qualified when something similar is done in the context of a namespace.

namespace A
{
class Impl *pimpl;
};

class A::Impl
{

};

int main() {
// your code goes here
return 0;
}

Answer

Well, the standard provides an answer as usual. And mandates it explicitly.

Quoting 3.4.4 [basic.lookup.elab] paragraph 2 (empahsis mine):

If the elaborated-type-specifier has no nested-name-specifier, and unless the elaborated-type-specifier appears in a declaration with the following form:

class-key attribute-specifier-seqopt identifier ;

the identifier is looked up according to [basic.lookup.unqual] but ignoring any non-type names that have been declared
(...)
If the elaborated-type-specifier is introduced by the class-key and this lookup does not find a previously declared type-name, or if the elaborated-type-specifier appears in a declaration with the form:

class-key attribute-specifier-seqopt identifier ;

the elaborated-type-specifier is a declaration that introduces the class-name as described in [basic.scope.pdecl].

And 3.3.2 [basic.scope.pdecl] paragraph 7 (emphasis again mine):

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

-- for a declaration of the form

class-key attribute-specifier-seqopt identifier ;

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

-- 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 ]

The declaration in the last emphasized line seems to refer to the declaration of the class where the elaborated class specifier appears. So in our case it adds the Impl into the namespace that contains the class A, the global scope. But the same will apply to any namespace. Case and point:

namespace E
{
    class A
    {
        class Impl *pImpl;  
    };
}

class E::Impl
{

};

int main() {
    // your code goes here
    return 0;
}
Comments