If I wanted to, could I just use only macros instead of using templates or functions without breaking anything?

Scoping could be an issue:

#include <iostream>
#define PRINT_VAR(x) std::cout << x << std::endl;

int main() {
    int a = 5;
    {
        int b = 10;
        PRINT_VAR(a);
        PRINT_VAR(b);
    }
    PRINT_VAR(a);
    // PRINT_VAR(b); // This would cause a compilation error if uncommented
    return 0;
}

But this is the case with functions as well. However macros may lead to multiple execuations like this:

#include <iostream>
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int main() {
    int x = 5;
    int y = 10;
    int z = MAX(x++, y++);
    std::cout << "x: " << x << ", y: " << y << ", z: " << z << std::endl;
    return 0;
}

What do you think the output here would be?

Answer
This is the output you get:

x: 6, y: 12, z: 11
This is because how the macro works is by replacing MAX with what it stand for, so z = MAX(x++, y++) becomes z = ((x++) > (y++) ? (x++) : (y++)) and if x = 5 and y = 10, in the evaulation x should become x++ i.e. 6 and similarly y becomes 11 and 6 > 11 is false so y is incremented again so it becomes 12. But the value assigned to z is not post the second incrementation but just before it, hence z = 11.


Now what if z is calculated as:

int z = MAX(++x, ++y);

Will the output be same as above?

Answer
No, what you will get now is:

x: 6, y: 12, z: 12
But why? Becuase the order of operations in x++ is different from ++x. In the first case, the value is assigned to z, then it is incremented. But in the second case, the value assigned is after it is incremented.