Hacker News new | past | comments | ask | show | jobs | submit login
Implementing Generic Types in C (btmc.substack.com)
89 points by sirwhinesalot 51 days ago | hide | past | favorite | 45 comments



> The former is nicer to program while the latter is nicer to use.

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.

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.

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.

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.


I wonder are there any existing metaprogramming frameworks for C? (something like C#'s Roslyn) I've been playing around with ANTLR and Python but curious to know what I've missed.


C++. Only half-joking, this is exactly how C++ started


Objective-C started that was as well, too!


It's worth mentioning that there are dedicated processors, like m4, and templating engines, like Jinja, if you need something battle-tested.


Probably worth mentioning the C Template Library [1]...

[1]: https://github.com/glouw/ctl/


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.


I often see such replies and wonder : the article isn't about choosing langs but is an exercise in C. That's the constraint.


If C is the constraint, then why are you doing generic types?

If you're using C, use bloody C. Not C with weird extensions that nobody will understand. Not C attempting to gussy up into a language with a real type system. Not C with a garbage collector. etc.

I don't get why so many people use C but don't want C. This isn't 1995 where you have to shoehorn everything into the C ecosystem because everything else sucked.


But ... all of those things are still C. Maybe with extra libraries / steps / whatever, but still C.


No ... this isn't C.

Will it have IDE support for the abstraction? Will a syntax mistake explode with a non-sensical error (yes, in this instance, because you are splicing macros and a syntax mistake will splice weird)? etc.

If you want C with Generic Types, use C++. Rather than splicing unknown garbage onto C, use a subset of your C++ features and you will get full IDE support, proper template errors (okay, maybe not an improvement), proper elision and inlining, etc.


> No ... this isn't C.

My test for whether something or not is "still C" is very simple... "Does it compile with a C compiler"

These compile. They're C. End of debate.

Whether something has IDE support is neither here nor there. When I was learning C, your IDE was "vi" and "csh"... It was still C.

As for errors, completely normal C code can spew out errors up the wazoo if you miss an errant ;, " or } (to pluck examples out of the air) somewhere important, things like "function not defined" because you were using a function later on in the file and it can no longer be parsed. Error quality does not define the language either.


All C compilers can do inline asm, is asm C too?


Jeez, there's always one..

Fine. My test for whether something consisting of C keywords, variable names, and symbols expected within the language as per the definition by some publicly agreed standard such as "C17", "C99" or "K&R", abhorring any non-standard extensions, other languages, or practices outside of the provided pre-processor, assembler and linker, and excluding any non-standard keywords that may or may not result in correct output, as pertaining to underlying architectural differences, is "still C" is very simple... "Does it compile with said C compiler"


See for example the last version, which is marked as "GOOD". It directly inlines all operations on the vector. The more you use it, the bigger the compiled code becomes. Need to debug it and want to set a breakpoint to vector_append? You can't. You also can't use callgrind/kcachegrind to see how fast the functions are. Yes, it's still C, but very awkward and finicky C.


My point was that this is still C, I wasn't advocating using any of it.

People have different preferences though, so if someone has 50,000 lines of code invested in a C application, but wants to (for some reason) use generics in some way, perhaps this is their best option...


Any compiler worth its salt be inlining those functions (provided lto is enabled) because inlining that type of tiny function is a win. Additionally, the linker can deduplicate identical functions (eg vec append for two identically sized types)


You could use C++ and ignore the rest of it.


I tried it once, ran out of memory debugging a template error as it blew up my terminal into a sea of jibberish.


Not sure why the parent is in gray. It's a perfectly valid and widely practiced approach.


How often it works out as intended is very much up for debate though.


For legacy codebases, switching the compiler is certainly out of the question. For everyone else, why do you think it would be an issue to use the C++ compiler?


Because the complexity seeps in through the cracks, and with the language comes a mindset; same reasons Linus is wary about C++ in the kernel.

Some say it works; I've never succeeded, even on solo projects.


I think this is the general style for the Godot framework. They use a limited subset of C++, avoiding STL and some modern features, and limited templates.


C3 and Odin are some other alternatives.


Along with D


D sits in a weird place and that's probably why it's not widely adopted.

If I'm using C, it's probably because I'm doing something low level and garbage collector could be problematic there. If you are fine with complexity, C++ or Rust are better options. If you want a simple language, I'd suggest Zig.

On the other hand, if I don't want to deal with memory management, and still want fairly fast compiled language, I'd just go with Go. It has a much better ecosystem, because low level GC language is really mostly targeted at server components and Go excels there with implicit async everywhere.


The creators of the D made a big mistake. Instead of focusing on improving the GC, they focused on making it possible to avoid GC in D. The Go has shown that this strategy was wrong.


Zig is nice. Comptime is a really cool feature.


This is how I do proper generic vectors in C, no template tricks needed and no pointer chasing involved:

https://github.com/codr7/hacktical-c/tree/main/vector

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.

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_.

https://github.com/JacksonAllan/CC


I've found useful doing an implementation with void* and having thin macros to do casting with typeof() so the usage is type checked.


> I personally quite enjoy programming in “C with methods, templates, namespaces and overloading”, avoiding the more complicated elements of C++ (like move semantics2)

Don't we all.

Except for the committee, of course, and its entourage.


Here's a b-tree library I wrote that uses a similar approach. https://github.com/tidwall/bgen


For linked lists and binary trees, intrusive data structures are better.

> Well, except the first one, template macros, where I can’t really find any pro, only cons.

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.

Type erasure is still type checked though, it's just lost at runtime (i.e. the generics are not reified).

This works for Java very well because the JIT will have another chance at further optimizations once the application runs.


> Type erasure is still type checked though

In the C example ? What do you mean ?


If I had to guess, they were moreso referring to the java examples... The article states "The biggest issue is the same one Java had before version 5, there’s no type safety"...

But that's a weird way to put it... Java 5 was when generics were actually introduced... so comparing a C hack for generics to Java pre V5... is just... weird.


Hi author here, I don't understand your comment. Why is it weird? It's the same issue, only difference is in java (pre-5) you'll get a runtime exception somewhere while in C you'll get memory corruption, but it's the same cause, an "any" type.

Modern Java has generics so it'll check at compile time that you use the types correctly.


> only difference is in java (pre-5) you'll get a runtime exception somewhere while in C you'll get memory corruption, but it's the same cause, an "any" type.

I would think that a runtime exception vs memory corruption is a big difference in relative type safety....

But overall I was just trying to explain to folks in thread how things were parsed....



Is this the version of "generics" the C committee is leaning towards? Last I checked there were at least three other proposals [1] [2] [3].

[1] https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3212.pdf

[2] https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2924.pdf

[3] https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2853.pdf


Unfortunately, the standardization committee plays around with macros and _Generic instead of investing their time in modern metaprogramming ideas


Lol, thats insane.

And awesome, I love it!




Consider applying for YC's Summer 2025 batch! Applications are open till May 13

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: