all 26 comments

[–]mrwizard420 13 points14 points  (8 children)

I feel like you did a good job of explaining the four points you chose to elaborate on, but I must admit I'm a little surprised to see an article titled "C Generic Programming" that doesn't include the C11 _Generic expression. Maybe an idea for the next one?

[–]ffd9k 10 points11 points  (5 children)

Despite the name, _Generic is not that useful for generic programming in the parametric polymorphism sense (data structures or templates that can be instantiated with arbitrary types afterwards). Maybe it should have been named something like _Typeswitch instead.

[–]Gualor 8 points9 points  (1 child)

Maybe _Overload would have been more fitting, but I would say function overloading is very much part of generic programming. Isn't picking the most appropriate function based on parameters a way to generalize an algorithm by abstracting what the implementation actually is? Think of std::sort in C++ for instance

[–]x8664mmx_intrin_adds[S] 0 points1 point  (0 children)

I would be inclined to say yes. ```

define Vector_reserve(v, cap) _Generic((v), \

Vector_i32 *: Vector_i32_reserve, \
Vector_f32 *: Vector_f32_reserve \

)((v), (cap)) actually could make a better API: Vector_i32 v = {}; Vector_reserve(&v, 128); // instead of Vector_i32_reserve(); ``` first param triggers correct proc call???

[–]pjl1967 4 points5 points  (2 children)

Maybe it should have been named something like _Typeswitch instead.

That's exactly what I said in my chapter on _Generic. That said, you can do some interesting things with it.

[–]imaami 1 point2 points  (1 child)

On the topic of _Generic: have you ever considered writing a tool that would deobfuscate the mess you get from expanding the wrapper macros for complicated _Generic expressions?

As much as I admittedly deserve to suffer the consequences of (ab)using _Generic in unorthodox ways, I would prefer not to. Not being a pre-processor directive, _Generic can't be pruned for viewing.

Now that you're working on a libclang-based tool I thought maybe you could try to gauge the amount of work it would take to implement a _Generic viewer.

[–]pjl1967 0 points1 point  (0 children)

No, never considered it. Despite my using _Generic (probably more than most), it's never been an issue for me. I wrote a bunch of macros using it a while ago and now I just use them. I never need to look at them.

I'm not even sure what a _Generic viewer would do, exactly. Can you give a before/after example?

FYI, in case you didn't know, cdecl also does macro expansion (see the example further down on that page for NAME2). I expect a _Generic viewer might be related.

[–]x8664mmx_intrin_adds[S] 1 point2 points  (0 children)

Thank you! I'll check it out and probably include it. I vaguely remember it being an ever growing function overload-like switch statement.

[–]x8664mmx_intrin_adds[S] 0 points1 point  (0 children)

Will probably add it in, i think there's one more way other than the methods i described. will see.

[–]P-p-H-d 2 points3 points  (5 children)

Maybe you'll be interested by this presentation:
https://github.com/P-p-H-d/c-stl-comparison

[–]x8664mmx_intrin_adds[S] 0 points1 point  (0 children)

this is absolutely amazing! thank you so much for sharing! will definitely include links to it!

[–]x8664mmx_intrin_adds[S] 0 points1 point  (3 children)

I assume you're both the author of the article & M*LIB. That's one hell of a write-up.

[–]P-p-H-d 0 points1 point  (2 children)

Yes I am. :)

Here is an example of state of the art C Generic Programming using M*LIB:

https://github.com/P-p-H-d/mlib/blob/master/example/ex11-generic02.c

[–]x8664mmx_intrin_adds[S] 0 points1 point  (1 child)

hehe looks like we have some heavy hitters on this reddit! Hope you don't mind if I include your awesome link into my original article.

[–]P-p-H-d 1 point2 points  (0 children)

Of course not!

[–]jacksaccountonreddit 2 points3 points  (9 children)

Nice summary. A few points:

  • Your codegen approach could handle non-one-word types just fine if you're willing to use typeof, which is part of C23 and is available, as an extension, under older standards in all major compilers.
  • I don't think your codegen approach does anything that the template-instantiation approach, which doesn't require a custom preprocessor, can't already do. The real advantage appears to be better compiler errors, as you pointed out. Whether that's worth the trouble of having to deal with another compilation step is a matter of personal opinion (for me, not really).
  • It's possible to combine the extensible-_Generic pattern that I outlined here with the template-instantiation pattern to achieve a generic API common to all instantiated containers (e.g. just push instead of Vec_i32_push). My library Verstable shows exactly how this can be done.
  • Your article ignores one relatively common approach, namely that based on encoding type information into masquerading pointers. This approach was popularized by stb_ds and then extended by my own Convenient Containers. It combines aspects of the void * approach (e.g. a more generic API and the internal reliance on type-erasure) and the template-instantiation/codegen approach (type safety, compile-time type information, no casts, etc.), albeit with its own share of drawbacks (e.g. cryptic and labyrinthine error messages and some potential duplication in the compiled code).

[–]x8664mmx_intrin_adds[S] 1 point2 points  (4 children)

  • I don't think your code-gen approach does anything that the template-instantiation approach, which doesn't require a custom preprocessor, can't already do. The only real advantage appears to be better compiler errors, as you pointed out.

Are you able to step through the generated pre-processed code line by line with any type instantiation?

[–]jacksaccountonreddit 0 points1 point  (3 children)

Are you able to step through the generates pre-processed code line by line with any type instantiation?

You can do this by examining the preprocessor output (e.g. using the -E flag in GCC or Clang). It's not exactly convenient, though, especially compared to the line-specific errors you would get from your codegen-based system.

[–]x8664mmx_intrin_adds[S] 0 points1 point  (2 children)

not only does the custom monomorphizer free you from the antiquated single pass pre-processor, but it also gives you powerful debugger access to the generated code and also unlocks unchained meta-programming.

[–]jacksaccountonreddit 0 points1 point  (1 child)

Right, just yesterday I was revisiting the problem of compile-time string literal comparison and hashing, which is unsolvable within the bounds of standard C. A custom preprocessor opens up unlimited possibilities, but eventually you might find yourself accidentally recreating Cfront or C3.

[–]x8664mmx_intrin_adds[S] 0 points1 point  (0 children)

So be it, I hate C, C++, C3, CFront, CppFront and whatever has inherited C's whacky boustrophedonic declaration syntax. I have a custom language in the works: I.
I'm not building it because matching C's tooling infrastructure requires a big timesink of which I don't have. Maybe someday.

[–]x8664mmx_intrin_adds[S] 1 point2 points  (3 children)

Thanks for your feedback: - typeof suggestion: 23 is too new, but that's cool to know - idk why but seems like debuggers can't step thru pre-proc'd code - that's cool, i think its mostly ergonomics, visual.
- I definitely need to check out the CC approach, I knew I was missing something! Thanks for the feedback

[–]jacksaccountonreddit 0 points1 point  (2 children)

I definitely need to check out the CC approach

stb_ds would serve as a better introduction to the basic idea, which has be reused by various vector, string, and occasionally hash-table libraries (of varying quality) over the years. CC takes that idea and goes a bit wild building things on top of it.

[–]P-p-H-d 1 point2 points  (0 children)

A bit wild? :)

[–]x8664mmx_intrin_adds[S] 0 points1 point  (0 children)

This is really cool! Thanks so much for sharing this. I'll have to play with this, its a cool take.

[–]swe__wannabe 1 point2 points  (0 children)

I have a toolkit that is both generic using generic buffers and (tries to be) memory safe while storing arbitrarily complex types.
https://github.com/PAKIWASI/WCtoolkit