It's safe if you cast back to its original type, the problem is that the users of the function have to be sure about the type of the arguments, there is no compile time checking compared to templates.
Another thing is that you have one level of indirection compared to templates.
If you really want templates in C there is the ugly macro solution:
1 2 3 4 5 6 7
#define template_max_decl(T) \
T max_##T(T a, T b);
#define template_max_def(T) \
T max_##T(T a, T b) \
{ \
return a > b ? a : b; \
}