Type Safe Generic Data Structures in C (2025)

2 days ago (danielchasehooper.com)

I don't understand people reinventing macro-based hacks in C to achieve what was achieved in other languages many years ago. Why not using C++, for example? It's available almost everywhere, introducing its usage in an existing C codebase is pretty simple. Sure, C++ has its own downsides, but is it better to create mess with macros in C rather then using exiting language facilities and standard library containers provided by C++?

  • Having used C++ a lot in the past, I think the mess in C++ is way worse. I also contribute to projects that introduce C++ into a C code base, and I really wish they hadn't done this.

    Although there are C features I like that I would need to remove before introducing C++ to a codebase, so this may not always be so simple.

    • > the mess in C++ is way worse

      What is mess in C++? Yes, it has some shady parts and is more complex than C, but this complexity provides expressiveness and type safety. And you can always use only features from C++ you find useful.

      > also contribute to projects that introduce C++ into a C code base, and I really wish they hadn't done this.

      What is problematic in these projects other than unfamiliarity of C++ for developers previously used only C?

      11 replies →

  • For one reason, it is because C++ runtime (and its standard library) is a whole own can of worms which most people would rather not touch if they can afford to. Which they mostly can.

    > Sure, C++ has its own downsides

    "Sure, getting your eyes gouged out has its downsides, but is it better to read that awful mess of macros in C instead?" The answer most people would give to this question may surprise you.

    • Here we go again. Yes there are bad parts in the C++ standard library and there are good parts. But if you are just trying to do type safe generic data structures you are unlikely to touch the bad parts. Many codebases forbids parts of the standard library, e.g. LLVM forbids including <iostream>. You can forbid using parts of the standard library too.

      19 replies →

  • Its rather hard to introduce c++ into legacy c projects. You basically have to decide on a subset of features to use, and then you'll have to explain to the teams why the same looking code now takes 10x more compute to build.

    Usually the way we do it here is we honor some interface then rewrite the subsystem in c++.

    And then there's the real hurdle and that is getting the c++ idiom correct as it is very easy to just open the floodgates and let everyone write code that looks vastly different.

  • I'd rather not migrate my C codebase to C++ just to use an array container. Very hard to consistently limit the codebase to a strict subset of C++.

  • Not sure why author does it, but I use C over C++ for one reason: I'd rather not have random hidden malloc() scattered in my code. And no, it's not about latency or performance. Once my code takes an yet unexplored part and runs out of 200kB RAM, the will be no way for me to learn about it - running out of memory will bring down both the logger and the radio link, which is the only way to get the logs from a sensor installed twenty meters above ground in a hazardous environment facility. C may be unsafe, but most errors short of "writing to a random memory area" are either well-contained or at least reproducible. Dynamic memory allocation means that anything may crash everything. Manageable if you have a MMU and can contain crashes within isolated heap, but if not - I'd rather not bother. And yea, you can write zero-allocation C++ code, but why bother if most of STL is now unusable? And you get some new and exciting UB modes (seriously, no union aliasing, what the heck?)

    Also, template metaprogramming is way more arcane than C macros. And I need compile-time metaprogramming in leu of dynamic allocation. So C macros it is for now (Zig is promising but still not there yet).

  • STL is amazing and the people that designed it were very smart.

    Problem is C++ has many features which I likely won't be able to understand in this lifetime. But using C-style C++ is nuts.

    Just the extra of STL is basically the C that C should have been. It's a shame not to have a string type or use brittle macros.

That `(1 ? (item) : (list)->payload)` was a neat trick. It gets optimized away, but the type comparison happens before that.

Still feels a bit like a party trick, but if it works...

  • It's somewhat of a forced trick nowadays, I think.

    I actually thought from the title before I read the article that it was going to use _Generic, as in something like _Generic((item),__typeof__((list)->payload):...) .

  • Expressions that compile but never evaluate are bread and butter in C++ template metaprogramming.

Is it feasible to create a new language by adding features to C

just as was done with C++?

  • It's not worth it. The value of C is in compatibility, availability of compilers, and familiarity.

    As soon as you change anything in a breaking way, start requiring your own compiler/transpiler, or introduce new idioms, you end up losing the things that keep C alive.

    Users of C either like it exactly the way it is, or have to use a specific C version due to a vendor dependency or compliance.

    An upgraded C not approved by the standards body gives you yet another niche language that is not C, but is still burdened with its old flaws.

    • Well said.

      I really don't understand why people bring up other languages when one is discussing implementation of some "advanced/tricky/hackish/new" features in C. It is not as if the implementer does not know about the ease of availability in other languages but there is always some set of criteria which prevents switching to a new language.

      On a related note, there is a dearth of written/learning material (books etc.) cataloging and explaining advanced architecture/design/implementation patterns in C though we know they exist in the tons of industrial-strength codebases out there.

      There is also the fact that when you see an implementation in C of some feature from another language you better understand language design pragmatics eg. implementing inheritance and virtual functions in C gives you insight into how they work in C++/Java/C#/etc.

    • It would be nice to have something like C which was very much like C and Pascal (not the syntax) but which was basically C except no naked pointers by default etc, fewer footguns, but which would generate C.

      1 reply →

  • I am working on a language that was C with extensions, transpiled to plain C, but the C syntax is kinda not great to work with (stuff like needing type tables and unbounded lookahead). At the point you clean up the syntax, you're not really C with extensions anymore.

  • C3 might be it because C3 has full C ABI compatibility so unlike most modern C-like alternatives, it checks out.

  • I mean you dont need to make a "language" at all, you can just write a librar for all the stuff you need. This stuff is just syntactic sugar.

    For example, when I worked on writing embedded software for autopilots, we had 2 files, common.so and common.h that contained essentially a super robust typing framework. The way it worked is that you defined types through the provided functions at the beginning of your code, and all of the definitions were stored and checked at runtime at the beginning before the main execution loop took effect. So basically taking what a more robust language compiler would do and just moving that processing to the start of execution.

    I use something similar for my local llm setup at home, where i can have my agent basically write tools for itself and when the tool fails, the error code makes it pretty easy for the agent to fix.

I absolutely cannot remember the details, but we did typesafe, macro based generic data structures in C in an undergrad class I took ~20 years ago (CMU Operating Systems). The idea has definitely been around, though I don't know if the implantation was the same.

As far as I know many C programmers think the addition of variable length arrays in C99 was a mistake. What would be the downside of this approach?

  • So much so, that it was made optional annex in C11, while Google paid to remove all their use from the Linux kernel.

  • I do not get the connection with a VLA in this context, but if you need a VLA, by all means use it. It is basically always better than the alternative.

  • VLAs are anti C. I also dont even think _Generics should be in the complier. The complier should do minimal processing of syntax in terms of decision making, with only focus on being optimizing code.

    Because in the flip side, you get C++ templates, which are turing complete, so you can write entire program syntax that the compliler executies during the complie process.

Also see Templates in C by David Priver - https://github.com/drpriver/drc

PS: I really like his style of writing and presentation; concise and precise without unnecessary fluff and page beautifying.

  • Lol at some point just implement a small compiler in C… reflection is kinda a hilarious thing to have like I understand the use case for generics, since it's not that that that hard to do with macros and quite commonly wished for

  • Hey that’s me! Cool to see people like my stuff.

    • You should write more ;-)

      I also posted your "Adding Reflection to C" at https://news.ycombinator.com/item?id=49964525 and hope HN picks up on it and has a decent discussion.

      You definitely should add a detailed post on your "DrC" compiler/interpreter; usecase, scope, techniques used etc. Since this is looking forward to C2y/C29 with some more extensions, i think people will enjoy playing with it in the REPL format.

      1 reply →

I just want C with classes, like C++ started out as.

What I'd really like is a standards-conformant (C++98/03) compiler that actually enforces the standard properly (or maybe it becomes a new standard), and ONLY allows that version to be used, AND only the classes.

No templates, no exceptions, no standard library (just use a plain libc).

Jesus, I hate those macro hacks so much. Most of the time you only need a single container, so writing an ad-hoc implementation is cleaner than that.

  • The extent some will go to avoid touching C++.

    • Indeed. After having suffered from C++ a lot, I am also really avoid touching it if I can.

      But if you compare some C++ template container to a C macro solution, I also do not find the macro solution to be more complex.

      2 replies →

I've tried doing this for a few years and it just sucks ass. Just use c++ and templates if you need proper generic data structures. C is a defective language.