Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

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.

"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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top