C++ / GETTING STARTED
The preprocessor: includes, guards, and macros to avoid
Use #include and header guards correctly, expand a macro the way the preprocessor does, and replace #define constants with constexpr and inline functions.
What you will learn
- Write a header guard and say exactly what the second #include of that header does
- Expand a function-like macro by hand: token paste, no types, no scope
- Replace #define constants and pseudo-functions with constexpr and inline functions
- Run g++ -E -P to see the exact text the compiler receives
Understanding The preprocessor: includes, guards, and macros to avoid
`#include` is not an import statement. The preprocessor locates the named file, splices its tokens into the current file at exactly that line and carries on reading, so a header reached twice in one translation unit really does arrive twice, and the second copy of `struct Point { ... };` is a redefinition error. An include guard makes the second arrival harmless: the first pass defines a macro, and every later pass sees `#ifndef` fail and throws away everything up to the matching `#endif`. Angle brackets search the compiler's configured include directories; quotes look next to the including file first and only then fall back to those directories, which is why your own headers use quotes.
`#define` creates neither a constant nor a function. It registers a name that the preprocessor replaces with a list of tokens everywhere that name appears later in the file, knowing nothing about types, scopes, namespaces or evaluation. The compiler therefore never sees your macro, only the substituted text, which is why diagnostics point at code you did not write and why a debugger cannot step into a macro. Two consequences cause most macro bugs: arguments are pasted in as raw tokens, so `SQUARE(2 + 1)` becomes `2 + 1 * 2 + 1`, and a parameter mentioned twice in the replacement is evaluated twice at run time.
The replacements are ordinary C++. `constexpr int limit = 100;` is typed, scoped, named in error messages and folded to a literal just as a macro would be, while an `inline` or template function evaluates each argument exactly once and takes part in overload resolution. Leave the preprocessor the jobs nothing else can do: include guards, `#if` blocks that switch on platform or build configuration, and capturing `__FILE__` and `__LINE__` at the call site. When an expansion surprises you, stop reasoning about it and print it with `g++ -E -P main.cpp`.
<iostream>
SQUARE(x)
LARGER(a, b)
constexpr int square(int x) { return x * x; }
int counter = 0;
int next_value() {
++counter;
return counter;
}
int main() {
std::cout << "SQUARE(2 + 1) = " << SQUARE(2 + 1) << '\n';
std::cout << "square(2 + 1) = " << square(2 + 1) << '\n';
std::cout << "LARGER(next_value(), 0) = " << LARGER(next_value(), 0) << '\n';
std::cout << "next_value() was called " << counter << " times\n";
}Directives are token-level text substitution performed before the compiler knows about types or scopes, which is both why include guards work and why macros misbehave.
Worked examples
A guard, expanded by hand
Shows what an include guard does when the same header text reaches the compiler twice in one file.
<iostream>
// What point.h contains.
TUTORIAL_POINT_H
TUTORIAL_POINT_H
struct Point { int x; int y; };
// The same header text arriving a second time.
TUTORIAL_POINT_H
TUTORIAL_POINT_H
struct Point { int x; int y; };
int main() {
Point p{1, 2};
std::cout << "p.x + p.y = " << p.x + p.y << '\n';
TUTORIAL_POINT_H
std::cout << "guard macro is still defined down here\n";
}Example explained
Line 1`#ifndef TUTORIAL_POINT_H` is true only the first time; the `#define` on the next line makes it false for the rest of the file.
Line 2The second `struct Point` is discarded before the compiler sees it; delete the four guard lines and g++ reports a redefinition of `struct Point`.
Line 3The `#ifdef` near the bottom proves the guard macro is not scoped to the block that defined it: it lives to the end of the translation unit.
Line 4Real headers use a name derived from the path, such as `GAME_MATH_POINT_H`, so two different headers cannot collide.
A macro has a position, not a scope
Demonstrates that substitution happens where the text sits in the file, regardless of functions and blocks.
<iostream>
SIZE
void report() {
std::cout << "report sees SIZE = " << SIZE << '\n';
}
int main() {
report();
SIZE
std::cout << "main sees SIZE = " << SIZE << '\n';
report();
}Example explained
Line 1`report` was expanded while `SIZE` still meant 4, so the literal 4 is baked into that function forever.
Line 2Directives are handled line by line, independently of C++ grammar, so the `#undef` and `#define` pair inside `main` is legal and affects only later text.
Line 3The second `report()` call still prints 4, which is the giveaway: the macro was never a variable that main could change.
Line 4A `constexpr int size = 4;` would obey normal scoping instead, and nothing written later could silently redefine it.
Important notes
`#pragma once` is not in the standard, but GCC, Clang and MSVC all support it; it keys on the identity of the file, so it can fail to deduplicate a header reachable through two paths or two copies, whereas a guard macro keys on a name and always works.
Do not name a guard `_POINT_H` or `__POINT_H__`: names with a leading underscore followed by a capital, or with a double underscore anywhere, are reserved for the implementation.
Common mistakes
Writing `#define MAX_ITEMS 100;` with a semicolon: the semicolon is pasted in too, so `for (int i = 0; i < MAX_ITEMS; ++i)` becomes `i < 100;` and the syntax error is reported at the loop, not at the definition.
Copying a header and forgetting to rename the guard, so two headers both use `#ifndef POINT_H`: whichever is included second is skipped entirely and you get 'was not declared' errors for types that are plainly written on disk.
Writing `#define SQUARE (x) x * x` with a space before the parenthesis: that defines an object-like macro whose replacement is `(x) x * x`, so `SQUARE(3)` expands to `(x) x * x(3)` and the compiler complains about an undeclared `x`.
Try it yourself
Change, predict, then run
In a single file, put `#define CUBE(x) x * x * x` next to `constexpr int cube(int x) { return x * x * x; }` and print `CUBE(1 + 1)` and `cube(1 + 1)`, then add parentheses around the parameters until the macro also prints 8. Then print `12 / CUBE(2)` and find the extra pair of parentheses the macro still needs.
Open the C++ workspaceCheck your understanding
You copy point.h to vector2.h but forget to rename the guard, so both files begin with #ifndef POINT_H followed by #define POINT_H. One .cpp includes point.h and then vector2.h. What happens?
- Everything in vector2.h is skipped, so its declarations never reach the compiler and errors appear wherever those names are used
- The second #define POINT_H is a macro redefinition error, reported inside vector2.h
- Both headers compile normally, because guard names only need to be unique within a single file
- vector2.h is included twice, so its class definitions collide
Show answer
By the time vector2.h is spliced in, POINT_H is already defined, so its #ifndef is false and every line up to the matching #endif, which is the whole header, is thrown away; the compiler then complains about unknown types at the use site, far from the real cause. Option 2 is tempting, but the second #define sits inside the region that was skipped, and even a repeated identical #define would be allowed anyway.