What it can't do is vary details of the implementation, at compile time, according to properties of the types operated on. C++ can do this. Rust can, to a degree. Go cannot, because it doesn't understand much about types. C macros cannot, because the macro system has no conception of types at all. The C optimizer can apply what it knows of types, but you have no way to tell it any more than it can puzzle out for itself.
If you are not used to a language that really supports strong types, you probably don't understand how much of programming work is offloaded onto the type system when using such languages. The C-grade "type checking" such languages provide is just the most trivial possible use of compile-time types. A powerful type system makes a language able to do automatically what you almost always cannot afford to do at all in a language without.
https://godbolt.org/ might be useful for this exercise.
#define SORT_ELEMENT_TYPE int
#define SORT_COMPARE(a, b) sign(a - b)
#include "specialized_sort.h"
And the header would define a function with the following signature:
int sort_int(int[] a, size_t n);
But generics provide another benefit: in theory with them, the compiler should be able to determine that two high-level types are actually the same machine type, so in the above example, SORT_ELEMENT_TYPE int and long would not generate duplicate code on many machines.