13

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?

underscore_d
  • 6,309
  • 3
  • 38
  • 64
peoro
  • 25,562
  • 20
  • 98
  • 150
  • This isn't a class just to let you know. –  Oct 30 '10 at 20:13
  • 11
    @thyrgle: Yes, it is. – Billy ONeal Oct 30 '10 at 20:14
  • 4
    @thyrgle: A `struct` and a `class` is the same except one default to `public` and the other `private`. – kennytm Oct 30 '10 at 20:15
  • @thyrgle: struct isn't a class in C, but in C++, struct is just the same as class except with `public` as default access specifier – Lie Ryan Oct 30 '10 at 20:15
  • @Billy: It is? Wth? It's a struct, isn't a class like the evolution of the struct or something like that? –  Oct 30 '10 at 20:15
  • @thyrgle: See KennyTM's answer. – Billy ONeal Oct 30 '10 at 20:15
  • @KennyTM: Yep, something which doesn't make sense – Lie Ryan Oct 30 '10 at 20:17
  • 3
    @KennyTM, Lie: I think he meant something more like - In C, a `struct` doesn't embody the idea of a class. In C++, a `struct` does embody the idea of a class. It is the same as a `class` with `public` as the default access specifier. – Merlyn Morgan-Graham Oct 30 '10 at 20:18
  • Not that this answers your question completely, but if you separate the class implementation from its forward declarations, then declare-before-use holds. This is commonly done with header and implementation file pairs (.h and .cpp). – Merlyn Morgan-Graham Oct 30 '10 at 20:22
  • 2
    That was mean to be C++ code. I used structs instead of classes just to avoid the needing of specifying public access... – peoro Oct 30 '10 at 20:26
  • @thyrgle, A struct is not "the same as a class except..." but it *is* a class. As is an union. – Johannes Schaub - litb Nov 01 '10 at 10:09
  • @NathanOliver Funny that you marked this question as duplicate of [SO: Do class functions/variables have to be declared before being used?](https://stackoverflow.com/q/13094898/7478597). -- It's nearly two years older. However, I agree that the duplicate link is helpful. – Scheff's Cat Feb 14 '19 at 14:41
  • @Scheff Age doesn't matter with duplicates. I felt the newer Q and answers are better so I decide to close this as a dupe of the new one. Feel free to cast a reopen vote if you want, or mod flag asking for merger instead. – NathanOliver Feb 14 '19 at 14:44

4 Answers4

9

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.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • 1
    For the standardese, see 9.2/2. The text at 3.4.1/8 is not sufficient. It just defines the scopes that are looked for for a name - if a name hasn't been declared yet in that scope, and there is no other rule (as such 9.2/2), it would be an error. This can be seen by trying to compile `struct A { void f(type); typedef int type; };` - 3.4.1/8 applies, but the code is not valid, because 9.2/2 does not apply. – Johannes Schaub - litb Nov 01 '10 at 10:31
  • Note that the rules for merely finding the name is also stated at `3.3.6/1b1`, but that rule is not sufficient - it would allow finding a name but it would not guarantee the parse state of its definition. For instance: `struct A { void f() { B a; } struct B { }; };`. This code is not valid if we only consider 3.3.6/1b1 without considering 9.2/2, because while we will find `B`, `B` will still be an incomplete class type. The rule at 9.2/2 will require the function body of `f` to consider `A` as if it already reached its closing brace, and will then find `B` as a complete class type. – Johannes Schaub - litb Nov 01 '10 at 10:41
  • However, I find it confusing that 3.4.1 uses the phrases "before its use ...". It seems it would be way clearer if it omitted those phrases and instead of saying "shall be declared in ..." it would say "is looked for in scope...". That way, names that are not in scope will not be found. Saying that "shall be declared in" is blabla, I think, and disrespects name hiding. 3.4.1/1 clearly says what happens: "In all the cases listed in 3.4.1, the scopes are searched for a declaration in the order listed" - so why do those subparagraphs use such bloated wordings. – Johannes Schaub - litb Nov 01 '10 at 10:50
8

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
  • 574,206
  • 118
  • 941
  • 841
  • 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 Oct 30 '10 at 20:35
  • 3
    @Blindy: Huh? You can't? You *can* actually. You *can* return instances of the class from a member function declared in that class. – AnT stands with Russia Oct 30 '10 at 20:39
  • Hrm nevermind, I wonder what I was remembering then... – Blindy Oct 30 '10 at 20:55
  • Yes. I was just wondering why C++ standard is like this. I guess DevSolar's answer is a good one. – peoro Oct 31 '10 at 11:27
3

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
  • 67,862
  • 21
  • 134
  • 209
1

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
  • 256,549
  • 94
  • 388
  • 662