1) You can use void*.
Which means you lose type safety.
2) You can use macros.
When debugging you can't step into functions that are macros. The compiler is pasting the same thing over and over into your code base, which can increase the size of your executable, and slow compile times, though I don't know by how much, but anything other than an instant compile does not feel good. If there is an error, the compiler spams messages up the screen, which is unpleasant. Complex macros are difficult to understand if you don't like thinking that way, especially if you haven't written them yourself.
3) You copy paste the same code over and over, with minor changes and cuts.
It avoids the problems of the first two, but it can bloat the code base like macros, but I'm not sure how bad it would be. Its not nice to do, because you have to do it all by hand.
***
I was thinking of automating the third option because I like things to be simple. The idea is a helper program that scans for commented markers in your project's source files, and uses them to copy paste generic functions with the correct type and name inserted into a single header file. That way I don't have to do any extra work to use them, other than type in a commented struct and hit compile. It would be like a stretchy buffer without macros.
Is this a bad idea? I haven't seen any code that does it, so I thought I'd ask.