I'm aware it's bad practice to do things like this [...]
Let's start off by emphasizing this once more: yes, this is horrible practice.
Now, what looks like macro overloading is actually macro dispatch, as covered in detail in e.g. the following Q&A:
Using the library code from the accepted answer:
#define CAT( A, B ) A ## B
#define SELECT( NAME, NUM ) CAT( NAME ## _, NUM )
#define GET_COUNT( _1, _2, _3, _4, _5, _6 /* ad nauseam */, COUNT, ... ) COUNT
#define VA_SIZE( ... ) GET_COUNT( __VA_ARGS__, 6, 5, 4, 3, 2, 1 )
#define VA_SELECT( NAME, ... ) SELECT( NAME, VA_SIZE(__VA_ARGS__) )(__VA_ARGS__)
we can define various FOR_N
overloads e.g. as follows:
// Dispatch macro.
#define FOR( ... ) VA_SELECT( FOR, __VA_ARGS__ )
// Dispatch for 1 or 2 macro arguments for dispatch function.
#define FOR_1(N) for(int i = 0; i < N; ++i)
#define FOR_2(I, N) for(int I = 0; I < N; ++I)
used as follows:
int main() {
int n = 3;
FOR(n)
FOR(j, n)
std::cout << i << " " << j << "\n";
/* 0 0
0 1
0 2
1 0
1 1
1 2
2 0
2 1
2 2 */
}
Note that what may look like (Pascalesque) scope of macroed for loops, is only limited to a single line, as they expand to
for(int i = 0; i < n; ++i)
for(int j = 0; j < n; ++j)
std::cout << i << " " << j << "\n";
and not
for(int i = 0; i < n; ++i) {
for(int j = 0; j < n; ++j) {
std::cout << i << " " << j << "\n";
}
}
To achieve the latter, you need to similarly explicitly add block scopes:
FOR(n) {
FOR(j, n) {
std::cout << i << " " << j << "\n";
}
}