- It gets you really garbage error messages, because the type errors are usually about something in the body of the template, wherever it tries to do something that the type you substituted doesn't support, rather at the offending call site.
- It hurts compile times (above and beyond proper generics), since you need to type check the generic function at every call site, rather than just once.
- It makes it easy to break interfaces, because exactly what is required of a type parameter isn't written down anywhere -- it's just whatever the body tries to do with it.
(Though it is also true that generics are certainly not a full substitute for macros. I would welcome some mechanism for doing codegen that didn't complicate the build system and was a bit lighter weight than what we have now).
Only early extremely non conforming compilers had macro-like template expansion.
Edit: although calling templates type level macros wouldn't be completely wrong.
Edit2: a better definition is that templates are type level functions: they take compile time values, types and other type functions and return values, types and type level functions.
The Template Haskell paper has some interesting things to say on the nature of C++ templates:
https://www.microsoft.com/en-us/research/wp-content/uploads/...
Unless you mean preprocessor macros in which case I'd rather cut myself.