It was a poor choice on the part of the author to use the macro-looking MAX() in his example. There is in fact a standard max() function in the <algorithm> header that does not suffer from silly macro-expansion problems:
template<class T>
const T& max(const T& a, const T& b) {
return a < b ? b : a;
}
template<class T, class Predicate>
const T& max(const T& a, const T& b, Predicate p) {
return p(a, b) ? b : a;
}
And your definition of MAX() is wrong, not just because of multiple evaluation of the arguments. The “conventionally wrong” MAX() would be this:
The problem is a loss of verbosity. The next person looking at the code isn't necessarily going to know how MAX is defined, and saving 1 line of code isn't worth making it less clear.
That said, for me, your code would be clear, as not being in all caps, I would assume it's a function and therefore doesn't have the same weakness.
As someone else pointed out, there's no way you can look at MAX(0,f()) and know that it's enforced that f() is only used once within the macro.