CodeGym /Courses /C++ SELF /Prototypes and code order

Prototypes and code order

C++ SELF
Level 14, Lesson 2
Available

1. Why the compiler "does not see" a function

When you start moving logic out of main into functions, a natural desire appears: write main first as a nice scenario, and put function implementations below — "details later". And here the compiler suddenly turns into a strict teacher: "I don't know what print_menu() is." The program looks logical, but the compiler is not a mind reader.

Imagine code like this:

#include <iostream>

int main() {
    print_menu();            // error: the function is still "unknown"
    std::cout << "Done\n";   // Done
}

If you try to compile this, the compiler will say something like: print_menu was not declared in this scope (or a similar message in your environment). The meaning is simple: at the call site the compiler has not yet seen its declaration.

And it's important to fix the idea: the compiler does not "run the program to see what happens". It first reads the file and builds a model of the program, and only after that turns it into executable code.

The compiler reads the file top to bottom

It sounds trivial, but in practice it accounts for half of all newbie errors related to functions. The compiler processes a single .cpp file as text: from the first line to the last. When it encounters a call to print_menu(), it must already understand what that entity is: whether it's a function, what parameters it has, what it returns.

You can imagine it like reading a book. If a character "Gandalf" appears in chapter one and the author only explains who he is in chapter five — the reader will be confused. The compiler, unlike the reader, does not like "I'll explain later." It prefers to have the "character's ID" shown in advance.

A small flowchart of how it feels:

flowchart TD
    A[Compiler reads the file top to bottom] --> B{"Encountered call name(...)?"}
    B -->|Yes| C{Does it know name's signature?}
    C -->|Yes| D[Checks arguments and compiles the call]
    C -->|No| E[Error: function not declared]
    B -->|No| A

From this comes today's rule:

A function must be declared before the place where you call it (within the file).

2. Declaration, definition and prototype

"Declaration" and "definition" — one name, two roles

To fix the "function not visible" problem, you need to distinguish two very similar terms: declaration and definition. Newcomers often think they are the same, but in practice they play different roles.

An everyday analogy: a declaration is like a sign on a door "Office 305, therapist". The definition is the actual doctor in the office who treats patients. If the sign is missing, you might not guess there is a doctor here until you accidentally enter.

The "sign" for a function is the prototype (that is, a declaration without a body):

int sum(int a, int b); // declaration (prototype)

And the "doctor in the office" is the definition (declaration + body):

int sum(int a, int b) { // definition
    return a + b;
}

Let's summarize compactly:

What it is How it looks Why it's needed
Declaration (prototype)
int sum(int a, int b);
Tell the compiler: "such a function exists, here is its signature"
Definition (implementation)
int sum(int a, int b) { ... }
Provide the actual code that will be executed

A small terminological remark: in standards and technical texts people more often use "declaration", and "prototype" is perceived as a colloquial simplification.

Function prototype: "show the passport in advance"

When you write a prototype you literally give the compiler the function's "passport": name, return type and parameter list. After that main can be above and definitions below, and everything will work.

Let's fix the earlier example with print_menu() correctly: add a prototype above main.

#include <iostream>

void print_menu(); // prototype

int main() {
    print_menu();
    std::cout << "Done\n";   // Done
}

Now the compiler already "knows" that print_menu is a function that returns nothing (void) and takes no arguments (()).

And where is the body? Below:

#include <iostream>

void print_menu(); // prototype

int main() {
    print_menu();
}

void print_menu() {            // definition
    std::cout << "1) Start\n"; // 1) Start
}

Note one small detail that loves to break compilation: a prototype ends with a semicolon. Just like a variable declaration. If you forget the ;, the compiler will assume you started a definition, and a cascade of strange errors will follow.

You can omit parameter names in a prototype

Yes, you can. The compiler cares about types and order of parameters, not names. Names are for humans (that is, for you next week) — for readability.

You can write:

int sum(int, int); // parameter names omitted

But this is more readable:

int sum(int a, int b); // names help to understand meaning

Usually in tutorial code it's better to include names almost always, because a prototype is not only "an instruction for the compiler" but also brief documentation: what the function expects as input.

Prototype and definition must match

There is a tricky situation: you added a prototype, the program started "seeing" the function, but then you changed the definition and forgot to update the prototype. Or vice versa. As a result the compiler may complain unexpectedly, because it checks calls against the prototype and then finds a definition that does not match.

For example, the prototype says the function accepts an int:

int square(int x); // prototype

But the definition suddenly accepts a double:

int square(double x) { // does not match the prototype!
    return static_cast<int>(x * x);
}

These are different functions (effectively), and the compiler will be unhappy. At best you get a compile error; at worst you will start "fixing the wrong line" because the diagnostics refer to a mismatch in declarations.

The rule is simple and useful: a prototype is a contract, a definition is the implementation of that contract. You cannot change the contract "silently" in one place.

3. How to organize code in a file

Can you just swap functions around?

You can — and sometimes that's even simpler. If a function is small and logically belongs above main, you can write its implementation before main, and no prototypes are needed.

This is also correct:

#include <iostream>

void print_menu() {
    std::cout << "1) Start\n"; // 1) Start
}

int main() {
    print_menu();
}

But once there are many functions, the file turns into a "wall of text" where main gets pushed down and you lose the idea of "main as a scenario". Therefore in real projects (and even in tutorial projects once you exceed ~50 lines) people often do this: prototypes on top, then main, then implementations.

This is not the "only correct style", but it is a good compromise for readability.

Mini file structure for tutorial projects

We're still working in a single file, without .hpp/.cpp separation and without modules. But we already want order. A good mini-structure for a tutorial project looks like this: include directives at the top, then prototypes, then main, then function definitions.

The skeleton looks like this:

#include <iostream>
#include <string>

void print_title();
int read_int(std::string prompt);

int main() {
    // program scenario
}

void print_title() {
    // implementation details
}

int read_int(std::string prompt) {
    // implementation details
    return 0;
}

The practical idea is simple: open the file and you immediately see the "public interface" (prototypes) and the main scenario (main). If you need details — scroll down.

4. Practical example: a mini-application NumberBuddy

Let's continue the "build something small but complete" line. Today it will be NumberBuddy: the program prints a header, asks for a number and prints the sum of its digits. The logic is simple, but we'll honestly practice code order and prototypes.

Start with prototypes

First describe what functions we have. It's like a table of contents: you can see what the program does without reading implementations.

#include <iostream>
#include <string>

void print_title();
int read_int(std::string prompt);
int sum_digits(int n);

Now the idea is visible: one function prints a title, another reads a number, a third calculates the digit sum.

main as a scenario

main will be short and "conversational": title → input → compute → output. Note that input and computation details are hidden, and main reads almost like a recipe.

int main() {
    print_title();

    int n = read_int("Enter a number: ");
    int s = sum_digits(n);

    std::cout << "Sum of digits = " << s << '\n'; // example: Sum of digits = 15
}

Even if you forgot how to compute the digit sum, main is still understandable.

Implementation of print_title()

Below in the file we place function implementations. Start with the simplest — printing the title.

void print_title() {
    std::cout << "=== NumberBuddy ===\n"; // === NumberBuddy ===
}

Implementation of read_int(...)

Make a "polite" function that prints a prompt and reads a number. We won't dive into robust invalid-input handling here (that's a separate topic); we'll implement a simple variant.

int read_int(std::string prompt) {
    std::cout << prompt; // Enter a number:
    int x = 0;
    std::cin >> x;
    return x;
}

Implementation of sum_digits(n)

The digit-summing function is a classic. Take the last digit using % 10, remove it with / 10, and repeat until the number becomes 0.

int sum_digits(int n) {
    if (n < 0) n = -n; // a bit of kindness to negatives

    int sum = 0;
    while (n > 0) {
        sum += (n % 10);
        n /= 10;
    }
    return sum;
}

Without prototypes the same code breaks

If you leave main at the top and definitions below but remove the prototypes, the compiler will reach print_title() and say: "I don't know what this is." And it will be right: it hasn't reached the definition yet.

That's why prototypes are not "formality for formality's sake", but a tool that allows you to write programs in a human-friendly order: scenario first, details later.

5. Common mistakes when working with prototypes and code order

Mistake #1: forgot the semicolon after a prototype.
This is one of the most annoying mistakes because it looks small but breaks a lot. A prototype is a declaration, and declarations in C++ usually end with a ;. If the ; is missing, the compiler expects a body {...} next and starts "guessing" about your code, often very creatively.

Mistake #2: prototype and definition do not match.
Sometimes you change a parameter type or add another parameter in the definition and forget to update the prototype. Then main is checked against the old contract, and a different function lives later in the file. The result is declaration/definition mismatch errors or mysterious diagnostics saying "definition does not match the previous declaration."

Mistake #3: confused function declaration with variable declaration.
Newcomers sometimes write something like int sum;, thinking they "declared the function sum". But a function always has parentheses (...). Even if there are no parameters you still need (): void hello();. Without parentheses it is a variable, and calling hello() will produce very strange errors.

Mistake #4: two definitions of the same function in one file.
With prototypes you can overdo things in the other direction: you copy an implementation, paste "another identical one" below and forget to delete the old one. Declarations may be multiple (identical), but the definition must be unique — otherwise the compiler/linker will complain about duplication.

Mistake #5: trying to "hide everything in prototypes" and losing readability.
Sometimes people want to write prototypes without parameter names to be "shorter". The compiler doesn't care, but you will. In tutorial code, parameter names in prototypes help a lot: int read_int(std::string prompt) reads like documentation. If you write read_int(std::string), in a few days you'll be wondering what that string was for.

1
Task
C++ SELF, level 14, lesson 2
Locked
Greeting Stand
Greeting Stand
1
Task
C++ SELF, level 14, lesson 2
Locked
Box parameters
Box parameters
1
Task
C++ SELF, level 14, lesson 2
Locked
Sum of digits
Sum of digits
1
Task
C++ SELF, level 14, lesson 2
Locked
Calculator operations
Calculator operations
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION