29

Take, for example, the following code:

#include <iostream>
#include <string>

int main()
{
    print("Hello!");
}

void print(std::string s) {
    std::cout << s << std::endl;
}

When trying to build this, I get the following:

program.cpp: In function ‘int main()’:
program.cpp:6:16: error: ‘print’ was not declared in this scope

Which makes sense.

So why can I conduct a similar concept in a struct, but not get yelled at for it?

struct Snake {
    ...

    Snake() {
        ...
        addBlock(Block(...));
    }

    void addBlock(Block block) {
        ...
    }

    void update() {
        ...
    }

} snake1;

Not only do I not get warnings, but the program actually compiles! Without error! Is this just the nature of structs? What's happening here? Clearly addBlock(Block) was called before the method was ever declared.

  • 7
    its actually a good question and there are people who wonder why this is. notably some answers to that come in form of "this is a historical reason, because two phase compilation was expensive back in 1976". but C++ is somekind of a bastard with some old parts and some new parts, thus you get this discrepancy. – v.oddou Jul 30 '14 at 08:23
  • 2
    Short answer: the function bodies are not compiled until after the class definition has been compiled – M.M Jul 30 '14 at 08:24
  • Practically speaking, this is feasible because the compiler has to backtrack just a single class, not an entire file. The latter was unreasonably expensive using 90's era technology. – MSalters Jul 30 '14 at 08:38
  • There was a similar question recently: http://stackoverflow.com/questions/24925831/why-did-c-never-allow-functions-to-be-used-before-theyre-declared#comment38731751_24925831 – Tony Delroy Jul 30 '14 at 09:24
  • Similar with classes too: http://ideone.com/Xw2dbF – user3791372 Jul 30 '14 at 14:48
  • 2
    In D&E, Stroustrup says this feature has been introduced to deal with name lookup issues. For example, `int i; struct s { void foo() { i = 42; } int i; };` When `foo` is defined outside the class, the `i` clearly refers to `this->i`, and it should be the same when moving the definition inside the class. – dyp Jul 30 '14 at 16:26

3 Answers3

15

A struct in C++ is actually a class definition where its content are public, unless specified otherwise by including a protected: or private: section.

When the compiler sees a class or struct, it first digests all the declarations within the block ({}) before operating on them.

In the regular method case, the compiler hasn't yet seen the type declared.

Russell Borogove
  • 16,687
  • 3
  • 37
  • 45
NirMH
  • 4,247
  • 2
  • 36
  • 59
  • @juanchopanza: It totally applies to inner types. "When the compiler sees a Class or Struct, it first digests all the declarations", which means the _declarations_ must be digestible. If a declaration uses an inner type, the inner type must be declared (maybe defined depending on the declaration). – Mooing Duck Jul 30 '14 at 16:58
  • @juanchopanza: I think we're having a communication error. I'm saying that the step "first digests all the declarations" implies that the declarations must otherwise follow the normal rules for types/signatures being declared _in order_. This recurses perfectly in inner types. After that, the definitions are parsed. I don't see this as an exception to inner types, I see this as inner types following the exact same rules as outer types. – Mooing Duck Jul 30 '14 at 17:08
  • @MooingDuck You're right, I hadn't read your first comment carefully enough. I guess you mean this: http://ideone.com/K40DDE, which makes perfect sense. – juanchopanza Jul 30 '14 at 17:47
13

C++ standard 3.4.1:

.4:

A name used in global scope, outside of any function, class or user-declared namespace, shall be declared before its use in global scope.

This is why global variables and functions cannot be used before an afore declaration.

.5:

A name used in a user-declared namespace outside of the definition of any function or class shall be declared before its use in that namespace or before its use in a namespace enclosing its namespace.

same thing just written again as the .4 paragraph explictely restricted its saying to "global", this paragraph now says "by the way, its true as well in namespeces folks..."

.7:

A name used in the definition of a class X outside of a member function body or nested class definition29 shall be declared in one of the following ways: — before its use in class X or be a member of a base class of X (10.2), or — if X is a nested class of class Y (9.7), before the definition of X in Y, or shall be a member of a base class of Y (this lookup applies in turn to Y ’s enclosing classes, starting with the innermost enclosing class),30 or — if X is a local class (9.8) or is a nested class of a local class, before the definition of class X in a block enclosing the definition of class X, or — if X is a member of namespace N, or is a nested class of a class that is a member of N, or is a local class or a nested class within a local class of a function that is a member of N, before the definition of class X in namespace N or in one of N ’s enclosing namespaces.

I think this speaks of all the code that does not stand in cpu executed code (eg declarative code).

and finally the interesting part:

3.3.7 Class scope [basic.scope.class]

1 The following rules describe the scope of names declared in classes.

1) The potential scope of a name declared in a class consists not only of the declarative region following the name’s point of declaration, but also of all function bodies, brace-or-equal-initializers of non-static data members, and default arguments in that class (including such things in nested classes).

2) A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule.

3) If reordering member declarations in a class yields an alternate valid program under (1) and (2), the program is ill-formed, no diagnostic is required.

particularly, by the last point they use a negative manner to define that "any ordering is possible" because if re-ordering would change lookup then there is a problem. its a negative way of saying "you can reorder anything and its ok, it doesnt change anything".

effectively saying, in a class, the declaration is looked-up in a two-phase compilation fashion.

v.oddou
  • 5,818
  • 3
  • 27
  • 55
5

"why can I conduct a similar concept in a struct, but not get yelled at for it?"

In a struct or class definition you're presenting the public interface to a class and it's much easier to understand, search and maintain/update that API if it's presented in:

  • a predictable order, with
  • minimal clutter.

For predictable order, people have their own styles and there's a bit of "art" involved, but for example I use each access specifier at most once and always public before protected before private, then within those I normally put typedefs, const data, constructors, destructors, mutating/non-const functions, const functions, statics, friends....

To minimise clutter, if a function is defined in the class, it might as well be without a prior declaration. Having both tends only to obfuscate the interface.

This is different from functions that aren't members of a class - where people who like top-down programming do use function declarations and hide the definitions later in the file - in that:

  • people who prefer a bottom-up programming style won't appreciate being forced to either have separate declarations in classes or abandon the oft-conflicting practice of grouping by access specifier

  • Classes are statistically more likely to have many very short functions, largely because they provide encapsulation and wrap a lot of trivial data member accesses or provide operator overloading, casting operators, implicit constructors and other convenience features that aren't relevant to non-OO, non-member functions. That makes a constant forced separation of declarations and definitions more painful for many classes (not so much in the public interfaces where definitions might be in a separate file, but definitely for e.g. classes in anonymous namespaces supporting the current translation unit).

  • Best practice is for classes not to cram in a wildly extensive interface... you generally want a functional core and then some discretionary convenience functions, after which it's worth considering what can be added as non-member functions. The std::string is an often claimed to have too many member functions, though I personally think it's quite reasonable. Still, this also differs from a header file declaring a library interface, where exhaustive functionality can be expected to be crammed together making a separation of even inline implementation more desirable.

Tony Delroy
  • 94,554
  • 11
  • 158
  • 229