Preprocessor macros & #define for CP
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
- Parenthesize everything: If you must write a macro, wrap every argument and the whole expression in `()`.
- Prefer constexpr: In SWE, replace macro constants with `constexpr int` and macro functions with `constexpr` functions.
Common mistakes
- Forgetting the outer parentheses: Writing `#define ADD(a, b) (a) + (b)` fails if you do `10 * ADD(2, 3)`. It expands to `10 * (2) + (3) == 23`, not 50. You need `#define ADD(a, b) ((a) + (b))`.
- Passing increments to macros: Never pass `i++` or `++i` to a macro like `MAX(a, b)` because the macro might expand it multiple times, e.g., `((a++) > (b) ? (a++) : (b))`.
Recall questions
- Why does #define SQ(x) x * x fail for SQ(3 + 2)?
- How do you properly write a macro that evaluates an expression?
- What is the modern C++ alternative to macro functions and why is it better?
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