Preprocessor macros & #define for CP

RoadmapsC++

Scenario

You define a macro to square a number: `#define SQ(x) x * x`. When you calculate `SQ(3 + 2)`, you expect 25, but your program outputs 11.

Why did a simple arithmetic macro give the wrong answer?

Mental model

The preprocessor is like a mindless find-and-replace tool running in Microsoft Word before your code even reaches the compiler. It knows nothing about C++ syntax, types, or order of operations.

Macros (`#define`) are processed before compilation. They perform naive text substitution. If you write `#define SQ(x) x * x` and call `SQ(3 + 2)`, the preprocessor replaces it literally with `3 + 2 * 3 + 2`. Due to operator precedence, this evaluates to `3 + 6 + 2 = 11`. To fix this, you must aggressively parenthesize macro arguments. Better yet, in modern C++, avoid macros and use `constexpr` or `inline` functions instead.

Explanation

In Competitive Programming (CP), macros are heavily used to save typing (e.g., `#define pb push_back` or `#define rep(i, a, b) for(int i=a; i<b; ++i)`). However, they are inherently dangerous because they don't obey C++ scoping rules or type checking.

When you use macros for expressions, you must wrap every argument in parentheses, and the entire expression in parentheses: `#define SQ(x) ((x) * (x))`. Without this, operator precedence will ruin expressions like `SQ(a+b)` or `100 / SQ(5)`.

Furthermore, macros can cause 'double-evaluation' bugs. If you call `SQ(i++)`, it expands to `((i++) * (i++))`, incrementing `i` twice and invoking undefined behaviour.

In modern software engineering (SWE), macros are strongly discouraged except for conditional compilation (`#ifdef`). You should use `constexpr` functions (which are typed, scoped, and evaluated at compile-time if possible) or `const` variables instead.

Code examples

The bug: Naive text substitution

#include <iostream>

#define BAD_SQ(x) x * x
#define BAD_DOUBLE(x) x + x

int main() {
    int a = 3;
    int b = 2;
    
    // Expands to: 3 + 2 * 3 + 2 == 11. Expected 25.
    std::cout << BAD_SQ(a + b) << '\n';
    
    // Expands to: 10 * 3 + 3 == 33. Expected 60.
    std::cout << 10 * BAD_DOUBLE(a) << '\n';
    
    return 0;
}

Because the preprocessor just blindly pastes text, the C++ compiler receives an expression with standard operator precedence. Multiplication happens before addition, destroying the intended logic.

The fix: Parentheses or constexpr

#include <iostream>

// Safe macro: wrap arguments AND the whole expression
#define SQ(x) ((x) * (x))

// Better: modern C++ constexpr function
constexpr int sq_func(int x) {
    return x * x;
}

int main() {
    int a = 3, b = 2;
    
    // Expands to: ((3 + 2) * (3 + 2)) == 25
    std::cout << SQ(a + b) << '\n';
    
    // Clean, type-safe, evaluates at compile time if inputs are constant
    std::cout << sq_func(a + b) << '\n';
    
    // Danger! SQ(a++) is still a bug: ((a++) * (a++))
    // sq_func(a++) works perfectly.
    return 0;
}

Aggressive parentheses fix the precedence issues, but macros still suffer from double-evaluation if you pass an expression with side effects. `constexpr` functions solve all these problems by acting as real, type-safe C++ functions.

Key points

Common mistakes

Recall questions

Questions & answers

A candidate's code contains `#define MAX(a, b) a > b ? a : b`. They use it as `int ans = MAX(x, y) + 5;`. Why is this a bug and how do you fix it?

It expands to `x > y ? x : y + 5`. Because `+` has higher precedence than `? :`, it evaluates as `x > y ? x : (y + 5)`. The fix is `#define MAX(a, b) ((a) > (b) ? (a) : (b))`.

Approach: Identify the lack of outer parentheses and the precedence of the ternary operator.

Why is `#define PI 3.14159` considered bad practice in modern C++?

Macros do not respect scope (they leak everywhere) and have no type. The modern, type-safe alternative is `constexpr double PI = 3.14159;` inside a namespace.

Approach: Compare macros to modern compile-time constants.

Continue learning

Previous: Arrays, strings & C-style vs std::string

Next: auto, range-for & structured bindings (C++17)

Return to C++ Roadmap