views:

157

answers:

4

I'm wondering why the declare-before-use rule of C++ doesn't hold inside a class.

Look at this example:

#ifdef BASE
struct Base {
#endif
    struct B;
    struct A {
        B *b;
        A(){ b->foo(); }
    };

    struct B {
        void foo() {}
    };
#ifdef BASE
};
#endif

int main( ) { return 0; }

If BASE is defined, the code is valid.

Within A's constructor I can use B::foo, which hasn't been declared yet.

Why does this work and, mostly, why only works inside a class?

+3  A: 

I don't know the chapter and verse of the standard on this.

But if you would apply the "declare before use" rule strictly within a class, you would not be able to declare member variables at the bottom of the class declaration either. You would have to declare them first, in order to use them e.g. in a constructor initialization list.

I could imagine the "declare before use" rule has been relaxed a bit within the class declaration to allow for "cleaner" overall layout.

Just guesswork, as I said.

DevSolar
+5  A: 

That's because member functions are compiled only after the whole class definition has been parsed by the compiler, even when the function definition is written inline, whereas regular functions are compiled immediatedly after being read. The C++ standard requires this behaviour.

Sven Marnach
That's also why you can't return instances of the class from inside the class definition (not references, but actual instances). The compiler can't tell yet how big your class is.
Blindy
@Blindy: Huh? You can't? You *can* actually. You *can* return instances of the class from a member function declared in that class.
AndreyT
Hrm nevermind, I wonder what I was remembering then...
Blindy
Yes. I was just wondering why C++ standard is like this. I guess DevSolar's answer is a good one.
peoro
+4  A: 

Well, to be pedantic there's no "declare before use rule" in C++. There are rules of name lookup, which are pretty complicated, but which can be (and often are) roughly simplified into the generic "declare before use rule" with a number of exceptions. (In a way, the situation is similar to "operator precedence and associativity" rules. While the language specification has no such concepts, we often use them in practice, even though they are not entirely accurate.)

This is actually one of those exceptions. Member function definitions in C++ are specifically and intentionally excluded from that "declare before use rule" in a sense that name lookup from the bodies of these members is performed as if they are defined after the class definition.

The language specification states that in 3.4.1/8 (and footnote 30), although it uses a different wording. It says that during the name lookup from the member function definition, the entire class definition is inspected, not just the portion above the member function definition. Footnote 30 additionally states though that the lookup rules are the same for functions defined inside the class definition or outside the class definition (which is pretty much what I said above).

Your example is a bit non-trivial. It raises the immediate question about member function definitions in nested classes: should they be interpreted as if they are defined after the definition of the most enclosing class? The answer is yes. 3.4.1/8 covers this situation as well.

"Design & Evolution of C++" book describes the reasoning behind these decisions.

AndreyT
+1  A: 

The most stubborn problems in the definition of C++ relate to name lookup: exactly which uses of a name refer to which declarations? Here, I'll describe just one kind of lookup problem: the ones that relate to order dependencies between class member declarations. [...]

Difficulties arise because of conflicts between goals:

  1. We want to be able to do syntax analysis reading the source text once only.
  2. Reordering the members of a class should not change the meaning of the class.
  3. A member function body explicitly written inline should mean the same thing when written out of line.
  4. Names from an outer scope should be usable from an inner scope (in the same way as they are in C).
  5. The rules for name lookup should be independent of what a name refers to.

If all of these hold, the language will be reasonably fast to parse, and users won't have to worry about these rules because the compiler will catch the ambiguous and near ambiguous cases. The current rules come very close to this ideal.

[The Design And Evolution Of C++, section 6.3.1 called Lookup Issues on page 138]

FredOverflow