Live data from Hacker News

Type-safe generic data structures in C

iafisher.com

21–30 of 57 posts

Re: Type-safe generic data structures in C

#21
post #15

Earlier quoted context omitted.

I agree that you shouldn't be doing this stuff in production if you don't know the ends and outs of doing this idiomatically. For instance, the lack of parenthesis ala #define SQ(x) ((x)*(x)) set off my C spidey senses. I use them even for constants in pound defines just to be consistent. That being said, there are benefits, like the type safety listed being one of them. No weird cast to void* and go to town on raw m…

You mean: #define SQ(x) ((x)*(x))

Yeah, thanks. That's what I get for trying to type code on my phone.

Re: Type-safe generic data structures in C

#22

Why would anyone use this when there is C++, Rust, etc? Genuinely curious.

Sometimes there isn't C++ or Rust. Many embedded platforms only have a C compiler. And sometimes an existing codebase is written in C, and adding some C++ or Rust creates undesired new dependencies. See the Linux kernel debate on using those languages.

Re: Type-safe generic data structures in C

#23
post #17

A better pattern instead of declaring via a macro that expands to a large number of functions is to define the implementation in a file, require the user to #define STACK_T and then #include the file. At the bottom of the file, #undef STACK_T. As files can be included multiple times, you can do this for each generic structure you like. A sort of poor-man’s templates, that you can reasonably step through in a debugger…

Note that include guards become tricky then.

You don't use include guards with this pattern. See https://en.wikipedia.org/wiki/X_Macro for more.

Re: Type-safe generic data structures in C

#24

Why would anyone use this when there is C++, Rust, etc? Genuinely curious.

Calling C++ or Rust code from languages other than C++ or Rust respectively involved much more accidental complexity compared to calling them from C.

Even calling C++ code from compilers other than the one you were using (e.g. you are using a 3rd party precompiled library) is a risky proposition.

Re: Type-safe generic data structures in C

#25

Why would anyone use this when there is C++, Rust, etc? Genuinely curious.

I favor the CTL over the STL anytime, because it compiles much faster, is much smaller, no indirect vtable calls, no bloated god objects, has no magic hooks attached, like dereferencing refs and ops, is clear on stack vs heap allocation.

The downside of course is that generics and iterators on those are a bit more troublesome, only compiler dependent goodies (mostly only clang, not gcc), like overloads or constexpr. C++ templates are ugly, and the STL is very buggy and limited. Also no security and no performance. They started on the wrong foot and had to keep it there.

Rust has an annoying syntax, but a much better library culture.

https://github.com/rurban/ctl

Re: Type-safe generic data structures in C

#27
post #9

Author here. I wish I had made it clearer that the intent of the post was "this is an interesting and surprising thing you can achieve in C" and not "this is a good idea for a real software project" or "this is a reason to use C instead of C++/Rust/Go".

Macros of this sort are indeed used to define stacks, queues, deques, and other generic data structures in the NetBSD (and presumably other BSDs) source code. So the pattern is used in real software projects.

Re: Type-safe generic data structures in C

#28
post #8

Why would anyone use this when there is C++, Rust, etc? Genuinely curious.

C is still, I have to imagine, the closest thing we have to a language available for all platforms that exist today, while C++ is heavily burdened with featuritis and Rust is limited in platform support.

> Rust is limited in platform support.

Rust supports everything you are likely to see in the wild outside of highly, highly specialized applications (e.g. ancient mainframes, satellites from the 80s or 90s, etc.).

For God's sake, it's becoming increasingly difficult to justify writing code that takes into account CPU endianness, because all CPUs that are likely to run your code are little endian!

Re: Type-safe generic data structures in C

#29

A better pattern instead of declaring via a macro that expands to a large number of functions is to define the implementation in a file, require the user to #define STACK_T and then #include the file. At the bottom of the file, #undef STACK_T. As files can be included multiple times, you can do this for each generic structure you like. A sort of poor-man’s templates, that you can reasonably step through in a debugger…

Yes! The include style of templates in C is way better than the old way of huge macros to instantiate code. The template code can look mostly like idiomatic C, it interacts way better with a debugger, it gives better compiler errors... everything about it is better and it's finally starting to become more popular.

I've open sourced my own C template library here:

https://github.com/ludocode/pottery

Not only does it use the #include style of templates, but it actually makes the templates composable. It takes this idea pretty far, for example having a lifecycle template that lets you define operations on your type like move, copy, destroy, etc. This way the containers can fully manage the lifecycles of your types even if they're not bitwise movable.

There's also this other more popular C template library, one that tries to more directly port C++ templates to C but with a lot less features:

https://github.com/glouw/ctl/

Re: Type-safe generic data structures in C

#30
post #19
post #16

Earlier quoted context omitted.

I've started moving to using python to generate what I need a lot of the time now in C. I'll either make a '.h' file, read it in and generate the boilerplate I need, or just do it using native python and emitting the C I need. I'm never again hand-writing an my_enum_to_str routine.

I share your feelings, though some years ago I discovered the neat trick of "xmacros". In a nutshell: #define COLOR_LIST \ X( RED ) \ X( GREEN ) \ X( BLUE ) \ X( PURPLE ) enum Color { #define X(code) code, COLOR_LIST #undef X }; static const char* colorNames[] = { #define X(code) #code, COLOR_LIST #undef X }; You can even make it cleaner and store more metadata: // --- color.def X( RED, "Red", 0xFF0000 ) X( GREEN, "G…

Yep, good old trick. Just don't forget to

  #undef X
when you're done.
Post reply on HN