3

This is related to a recent question.

Basically the following code:

class A
{
    class B* b;
    B* c;
};

compiles although class B is not declared or forward-declared. Is this syntax equivalent to a forward declaration? Are there any differences?

Community
  • 1
  • 1
Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • I don't see how this is fundamentally different to the question to which you have linked: [Pointer to a structure that has not been declared](http://stackoverflow.com/questions/9148258/pointer-to-a-structure-that-has-not-been-declared) – CB Bailey Feb 05 '12 at 10:03
  • 2
    @CharlesBailey: Well, this question is C++, not C, and C++ tends to be fussier about requiring forward declarations (e.g. the code snippet in the linked question won't compile in C++). – j_random_hacker Feb 05 '12 at 10:11
  • 1
    @j_random_hacker: Good pointer, didn't notice "C" in the linked question, but why wouldn't the linked question not compile as C++? It looks fine to me and to `g++ -std=c++98 -pedantic` . – CB Bailey Feb 05 '12 at 10:14
  • Yes, the linked question is valid C++. What's different in C++ is that, because classes don't share a single namespace, the rules for determining which namespace a class declaration refers to can be a bit complicated. –  Feb 05 '12 at 10:28
  • You're right @CharlesBailey, the linked question is indeed fine C++ ! My mistake. My new interpretation of the difference between these questions: this question (unlike the linked one) is asking about whether a forward declaration has, or should have, a "persistent effect" (namely, allowing *future* definitions involving `B` to work without writing `class`) when it takes the form of a definition, as it does here. – j_random_hacker Feb 05 '12 at 13:07

3 Answers3

9

You can declare a type and an object in the same declaration.

class B* b;

Declares a type, B and an object b which has type pointer to B. The type is incomplete and is looked up in the scope in which it occurs, if the lookup fails to find an existing declaration for the class then the type names a type in the nearest enclosing namespace scope (strictly non-class non-function-prototype scope, which is usually a namespace). The object is a member of the scope in which the declaration appears (in this case, class A).

In most cases it's more common to declare a complete type and an object together, in this case the type is sometimes left anonymous. E.g.

struct { int a; int b; } x;

The relevant parts of the standard for the name scoping rules are 7.1.5.3 [dcl.type.elab] Elaborated type specifiers / 2 and the referenced sections in 3.4.4 and 3.3.1 :

3.4.4 describes how name lookup proceeds for the identifier in an elaborated-type-specifier. If the identifier resolves to a class-name or enum-name, the elaborated-type-specifier introduces it into the declaration the same way a simple-type-specifier introduces its type-name. If the identifier resolves to a typedef-name or a template type-parameter, the elaborated-type-specifier is ill-formed. [ ... ] If name lookup does not find a declaration for the name, the elaborated-type-specifier is ill-formed unless it is of the simple form class-key identifier in which case the identifier is declared as described in 3.3.1.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • This is slightly inaccurate, I believe: `B` is not a member of `A`, but refers to `::B` and is in scope even outside the definition of `A`. –  Feb 05 '12 at 10:18
  • Although if another `B` was already in scope, for example a declaration consisting of no more than `class B;`, *then* it could refer to a nested class `A::B`. –  Feb 05 '12 at 10:20
  • It's actually the nearest non-class non-function-prototype scope, which rarely matters, but consider `void f() { struct s { struct p *ptr; } s; struct p { } p; s.ptr = &p; }` :) –  Feb 05 '12 at 10:40
1

No, it's a declaration, of a pointer to B. You are not declaring B here, only a pointer to it, and there's nothing forward about it.

Edit: I was wrong, sorry. See other answer.

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
1

I would like to add few details to answer of Charles Bailey:

class A
{
public:
    class B * b;
    class C {} *c;
    int d;
} a;

B* globalB;
// C* globalC;  identifier "C" is undefined here

int main(int argc, char *argv[])
{
    a.d = 1;
    cout << a.d;
}

Yes, it defines incomplete type B and b as a pointer to B at once. But here comes the fun:
"An exception to the scope visibility of a nested class declaration is when a type name is declared together with a forward declaration. In this case, the class name declared by the forward declaration is visible outside the enclosing class, with its scope defined to be the smallest enclosing non-class scope." (Nested Class Declarations)

Which means that type B is defined out of scope A which allows you to define variable globalB. If you don't want B to be defined out of scope A, you can use {} just like it is used with type C in my example.

By the way, this example is correct and its output is: 1

Community
  • 1
  • 1
LihO
  • 41,190
  • 11
  • 99
  • 167