Sometimes macro's just save you from typing. But like many other basic tools, their heavily abused.
Indeed, they are too easily abused and frequently abused.
I through about removing the macro-preprocessor from C2, but still haven't found a good solution
as alternative. One idea are semantic macros. You can define these like functions, but the main
difference with normal macro's is that they are expanded inside the AST (Abstract Syntax Tree)
and not in the source code. This way, everything can be checked. For example:
old and very bad way
#define MAX(a,b) (a>b? a : b)
With semantic macros, this would be:
public macro MAX(a, b) {
a>b? a : b;
}
The nice thing is that the compiler understands it a lot better and that
it can actually warn if used with the wrong a and b for example.
That use case is pretty much a function.
Expanding it in the style a macro achieves two things there:
- Inlining for performance
- Code is applicable for multiple types (generics/templates in other languages)
For the inlining performance case, I would argue that 1) that is an issue for optimizer, 2) the inline keyword should force it to occur.
Also, inlining the MAX in this example seems reasonable, but when the uses escalate to very large amounts of code being inlined it can become evil again.
The semantic macro in your example is an improvement.
Another use of macros I've seen, which is really an inlining issue, is for table gen type stuff.
Example:
http://hg.dersaidin.net/weaver/file/17e883a72fc6/base/code/game/spell_info.defhttp://hg.dersaidin.net/weaver/file/17e883a72fc6/base/code/game/spell_shared.c#l352But this could be done using a constant global to hold the data/table, and a for loop instead of a switch. I believe the reason for using macros instead is: to have a switch, which should gen more efficiently than a loop.
I think undefining and redefining macros like this would still be evil with semantic macros.
You could also argue this type of thing should be done using a tool like LLVM's tablegen.
The most common place macros are used for inlining is for constants. That is replaced with c++11 constexpr (or just const globals, I think it is really a pretty arbitrary difference).
Generics/templates in other languages could be described as "semantic macros", but they are are more strongly typed than the MAX code in your example:
template<typename T>
T MAX(T a, T b) { return a > b ? a : b; } // a and b are both type T.
#include <iostream>
int main(int argc, char**argv) {
int a = 4;
int b = 9;
float c = 4.99;
float d = 9.52;
std::cout << MAX(a, b) << std::endl;
std::cout << MAX(c, d) << std::endl;
// Error, they're not the same type
std::cout << MAX(a, c) << std::endl;
return 0;
}
All of the reasonable macro expansions uses I can think of are either
- Inlining to work around optimizer failings
- Generics
What other uses of macros expansions aren't totally evil?
There might be some, but I can't think of any others at the moment.