> The former is nicer to program while the latter is nicer to use.<p>When I have such a situation, I'm inclined to write myself my own pre-processor (as I did for Pascal once in a previous millenium, on an Atari ST 520+), so that you can write in a style that is nicer to program in, which gets pre-compiled into something that is nicer to use from your client code.<p>Nothing comes without downsides: the price of this is that other developers need to understand your idiosyncratic pre-processor, so this method works best for "single author" code/personal projects.<p>What you don't want in a team is each coder having their own way of doing things, so that you cannot share libraries and helper functions.<p>BTW, the best book on the OP's topic of production coding in C and implementing type-safe ADTs is Chris Hanson's book "C: Interfaces and Implementations." It contains some eye-opening tricks even for experienced developers in (standard) C.
For C with generic types, I think one should pick up Zig. It's exactly that, it's a low level language with manual memory management, but it allows you to do generic types like this without hacks.
This is how I do proper generic vectors in C, no template tricks needed and no pointer chasing involved:<p><a href="https://github.com/codr7/hacktical-c/tree/main/vector" rel="nofollow">https://github.com/codr7/hacktical-c/tree/main/vector</a><p>The problem was always pretending C is something it's not, that never works well in any language.
When moving into generic types and metaprogramming in C, your only real choice is macros. I've been down that path. It just never seems to work out very well, and the results were always unsatisfying.<p>At some point, it becomes worthwhile to graduate to a more powerful language that still retains all the low level capability, like DasBetterC.
I've never heard of CC before; the ergonomics of it look positively _modern_.<p><a href="https://github.com/JacksonAllan/CC" rel="nofollow">https://github.com/JacksonAllan/CC</a>
> <i>I personally quite enjoy programming in “C with methods, templates, namespaces and overloading”, avoiding the more complicated elements of C++ (like move semantics2)</i><p>Don't we all.<p>Except for the committee, of course, and its entourage.
Here's a b-tree library I wrote that uses a similar approach.
<a href="https://github.com/tidwall/bgen" rel="nofollow">https://github.com/tidwall/bgen</a>
For linked lists and binary trees, intrusive data structures are better.<p>> <i>Well, except the first one, template macros, where I can’t really find any pro, only cons.</i><p>For toy examples, the first (expanding a huge macro) has mostly cons. But it is more flexible when you want to instantiate different parts of the header. The second approach can work but will be clumsy in this case because the whole header is considered as one unit.
The Java example doesn't really compile due to generic arrays and mixing primitives and non-primitives, but the point still holds.<p>Type erasure is still type checked though, it's just lost at runtime (i.e. the generics are not reified).<p>This works for Java very well because the JIT will have another chance at further optimizations once the application runs.