Skip to content
Theory · Building and the Preprocessor · lesson 2 of 8

The preprocessor and macros

In the previous lesson, #include put a copy of the memo into each .cpp file's folder. That work is not done by the compiler. Before it, a clerk goes through the files: the clerk carries out notes with instructions and pays no attention to what the papers mean. One note says "put a copy of triple.h here," another says "replace the word MAX_USERS with 100 everywhere."

The preprocessor

In C++, the clerk is the preprocessor. It reads a file before the compiler does and carries out its directives—lines that start with #. The #include directive replaces itself with the text of the named file, word for word, checking nothing in it. The compiler receives finished text with no #include directives left in it.

The preprocessor knows nothing about types or functions. It works with text alone, and everything that happens with macros follows from that.

Macros

The #define directive creates a macro—a name that the preprocessor replaces with the given text. It is like "Find and replace" in a text editor: the replacement works on the text, with no calculation. A macro can stand for a constant, or it can take parameters like a function:

C++
#include <iostream>

#define MAX_USERS 100
#define SQUARE(x) x * x

int main() {
    std::cout << MAX_USERS << ' ' << SQUARE(7) << '\n';  // 100 49
}

What reaches the compiler is already 100 and 7 * 7. The preprocessor does not evaluate a macro's argument: it pastes the argument's text in place of x as is and adds no parentheses. The compiler then evaluates the resulting expression with the usual operator precedence.

Macro names are written in all caps so that nobody mistakes them for variables and functions. For constants, constexpr variables are used instead of macros: they have a type, and the compiler checks them. Interviewers like to ask why a macro is worse than constexpr, and the answer starts here.

Common mistake

The macro halves its argument, but the result is wrong:

C++
#include <iostream>

#define HALF(x) x / 2

int main() {
    int result = HALF(10 + 4);  // ✗
    std::cout << result << '\n';  // 12
}

Neither the compiler nor the program reports an error. The preprocessor turned HALF(10 + 4) into 10 + 4 / 2, and division comes before addition.

Write a constexpr function instead of such a macro:

C++
#include <iostream>

constexpr int half(int x) {
    return x / 2;
}

int main() {
    constexpr int kHalf = half(10 + 4);
    std::cout << kHalf << '\n';  // 7
}

The function receives its argument already computed—14. The word constexpr before a function lets it be called at compile time when its arguments are constants. Its result can then initialize a constexpr variable, like kHalf here.

Key points

  • The preprocessor carries out directives before the compiler and works with text alone.
  • #define creates a macro—a name that is replaced with text, with no calculation.
  • A macro's argument is pasted as is, so HALF(10 + 4) turns into 10 + 4 / 2.
  • Constants and calculations are written with constexpr variables and constexpr functions.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way