22 Beginner The Hundred
The Macro That Did the Wrong Math
a 4×4 square with a suspicious area
#include <iostream>
#define SQUARE(x) (x * x)
int ids = 0;
int next_id() { return ++ids; }
int main() {
int side = 3;
int border = 1;
std::cout << "framed area: " << SQUARE(side + border) << '\n';
std::cout << "id squared: " << SQUARE(next_id()) << '\n';
std::cout << "ids handed out: " << ids << '\n';
}
Run it. How big is a 4×4 square, and how many ids get handed out?
Answer
7, 2, 2 — not 16, 1, 1. The square lost nine units of area, and one id was
quietly spent twice.
Why¶
A macro is not a function: the preprocessor pastes the argument's text into the body and
the compiler parses whatever comes out. So SQUARE(side + border) becomes
(side + border * side + border) — the outer parentheses were never the problem, because
* binds tighter than +, and the result is 3 + 1·3 + 1 = 7. And SQUARE(next_id())
becomes (next_id() * next_id()): the argument text appears twice in the body, so the call
happens twice, yielding 1 and 2 (in unspecified order) for a product of 2. -Wall -Wextra
is silent on both — by the time the compiler sees this, it is ordinary, well-formed
arithmetic. Parenthesizing each parameter (#define SQUARE(x) ((x) * (x))) fixes the
precedence, prints 16, and still calls next_id() twice; with SQUARE(i++) that double
evaluation is undefined behavior — modifying i twice with nothing sequencing the two
(GCC 11.5 does warn there: operation on 'i' may be undefined [-Wsequence-point], on
under plain -Wall). When a macro misbehaves, g++ -E main.cpp shows you the text it
really produced.
The fix¶
Use a function. It respects precedence, evaluates its argument exactly once, and
constexpr keeps it foldable at compile time:
constexpr int square(int v) { return v * v; }
square(side + border); // 16 — the argument arrives as a value, not as text
square(next_id()); // one call, one id
Takeaway: macros paste text, functions take values — if it computes something, make it
a constexpr function.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo