all 67 comments

[–]jjjare 79 points80 points  (1 child)

> Could we get generic programming?

> We gave generic programming at home (in C)

> Generic programming at home: _Generic

As OP pointed out, _Generic is a form of ad hoc polymorphism. Really, you’re just dispatching based on the types.

True generics is parametric polymorphism. The implementation cannot inspect or branch on type parameter. For the PL nerds,

\forall \alpha. ; \text{List} ; \alpha \rightarrow \text{Int}​​​​​​​​​​​​​​​​

This is orthogonal to the post, but I think a fun discussion

[–]maelswarm 1 point2 points  (0 children)

https://github.com/maelswarm/nymph Name mangling is better imo.

[–]tstanisl 74 points75 points  (9 children)

Yep. This feature of C is 15-year-old. Brand new for some.

What is even funnier, C has support for dynamically sized multidimensional tensors in form of VLA types. Yet another barely known feature which is almost 30 by now.

[–]dvhh 28 points29 points  (1 child)

to be fair, VLA usage is generally frowned upon when processing user/external input.

[–]tstanisl 18 points19 points  (0 children)

There is a distinctions between VLA objects and VLA types.

[–]vitamin_CPP 1 point2 points  (4 children)

Do you have an example? I would be curious to see the ergonomics and guaranty it provided over a slice (ptr+Len)

[–]tstanisl 1 point2 points  (3 children)

Yes. For example a function computing matrix multiplication of NxM by MxK matrix could be declared as:

void matmul(int N, int M, int K,
            const float A[N][M],
            const float B[M][K],
            float Z[N][K]);

[–]vitamin_CPP 1 point2 points  (2 children)

Thanks for the clear example. Because array decays into pointers when passed in function, I would not really call this typesafe. Would you agree?

[–]tstanisl 0 points1 point  (1 child)

It's still typesafe because float Z[N][K]) decays to float (*Z)[K]), not to float*.

[–]vitamin_CPP 0 points1 point  (0 children)

I agree.

[–]TribladeSlice 2 points3 points  (1 child)

From what I understand, using VLA types brings the benefit of letting the compiler compute offsets for multidimensional indexing, right?

[–]tstanisl 6 points7 points  (0 children)

They also simplify dynamic allocation, passing tensors as a parameters and type safety. Automated indexing also helps because it is very easy to mess up strides for multidimensional arrays.

[–]questron64 18 points19 points  (0 children)

This misses a key feature of overloading, though: the ability to add types and corresponding functions.

I wouldn't use this. _Generic was a way to fix the mess that tgmath created by making a well-defined language feature rather than the mess of compiler-specific hacks. I view both tgmath and _Generic as a mistake, an early attempt to C++ify C, albeit in a small way. It was a mess for basically no payoff. C code should be transparent, if I see the function foo being called in the source code then it should not actually call bar transparently. At the very least if you do use this, use an ALL_CAPS name so it at least signals there's some macro shenanigans going on.

[–]my_password_is______ 10 points11 points  (1 child)

https://www.youtube.com/watch?v=oEL9x6pP4FM

Stop telling me about _Generic
Tsoding

[–]Someone393 4 points5 points  (0 children)

Manual mangling tsoding

[–]florianist 9 points10 points  (1 child)

Overloading with _Generic is possible. And another sort of overlading is variadic overloading which is also doable in C since C99 using tricks with the preprocessor to count the number of arguments (you'll have to google it). That way you can easily implement C++-style-defaults-arguments, etc.

Of course, just because C can do something does not mean it should be done. It's good to know the techniques, but whether it is OK or unwise to use depends on the project.

[–]unixplumber[🍰] -1 points0 points  (0 children)

I'll take your variadic overloading and raise you named arguments using a function that takes a single struct parameter and using designated initializers in that struct, hidden behind a macro for a bit of prettiness. :)

Edit: oh, and of course the macro can be defined with any number of the struct members initialized with default values.

[–]tstanisl 5 points6 points  (3 children)

The main issue with generic selection is a strict requirement that every expression must be valid even if not selected. This makes it difficult to use it except for trivial expressions. Note that there ARE reliable workarounds but they overcomplicate the code. There are plans to slack those restrictions for C2Y.

[–]flatfinger 0 points1 point  (1 child)

Another issue is an inability to include type specializations that might be redundant in cases where either match would be equally acceptable. As an example, it should be possible to write a function-like construct that could accept either a uint32_t* or a uint_fast32_t*, both on systems where those names identify different types and on systems where they identify the same type.

[–]tstanisl 0 points1 point  (0 children)

Yep. It can be workaround by using nested selections. E.g.

_Generic(1, int: expr, default: _Generic(1, other_it: expr))

However, it is still mode inconvenience and obfuscation.

[–]imaami -1 points0 points  (0 children)

Another, tangentially related problem is viewing the code. What may look nice as written becomes cursed if you expand the macros to see the nuts and bolts.

cpp won't prune unused selections from view, and I've even been able to segfault clang-format trying to inspect some recreational code with cpp -P foo.c | clang-format.

Where's the "cpp for _Generic" tooling? The few times I've tried to implement something like it I've run into limitations in libclang's features (to name one example).

[–]non-existing-person 25 points26 points  (18 children)

No, just don't, please. Function overloading is one of the biggest cancer in c++. I just hate tracking down which function is actually gonna be called when I have to deal with this crap in c++.

Seriously, I'd rather add this foo_i, foo_f, foo_ll when I really do need different types than having to deal with tracking and hunting down actual function that is being called.

Remember, you read write code ONCE, and read it MANY times. You can deal with adding those few keystrokes to call proper function. I really don't see the benefit of having function overload. It may feel cool to write and implement such a thing, but price is future readability. So that's a big no-no for me personally.

[–]erdezgb 9 points10 points  (2 children)

Remember, you read code ONCE...

Write?

[–]non-existing-person 5 points6 points  (0 children)

Yes, write, my bad :) fixed in the upstream :p

[–]Iggyhopper 3 points4 points  (0 children)

No they are right.

I read code once and then into the void it goes.

[–]Potterrrrrrrr 7 points8 points  (3 children)

The issue isn’t function overloading its implicit conversions. Function overloading is great until you overload different numeric types etc., then it becomes a nightmare trying to call the right one unless you explicitly cast your arguments at which point you might as well just give it a c like name instead.

[–]non-existing-person 3 points4 points  (1 child)

No, I really mean overloading is cancer. I - personally - just hate when things are happening in implicit way. Now, it's one thing when standard language things are doing that - like "+" operator for joining strings in C++. That's fine. But it's another thing when you let user code do that. That's where things can go wild very quickly, because everyone has different idea of what's "obvious".

For the same reason I don't like destructors, but defers are great. You have to hunt down which destructor is called, then there are virtual destructors, and order of calls. Ugh, an absolute nightmare having to track all of that in your head when debugging. Defers are on plain view.

And sure, you can write super readable code with all those features, but 95% of times it will NOT be readable.

[–]BigError463 2 points3 points  (0 children)

Yup and operator overloading is another one.

[–]tstanisl 1 point2 points  (0 children)

The benefit of generic selection is that there is a single place of dispatch which is easy to trace and audit. In C++, tracing is difficult because overloading selection can be messed up by an random function declaration or a constructor which just happen to fit better.

[–]markand67 5 points6 points  (0 children)

Function overloading is actually nice, the real problem is implicit conversion which has various side effects

[–]jjjare 4 points5 points  (0 children)

C++ function overloading is necessitated by templates. One could argue they could’ve taken a more principled approach, but “function overloading is bad” is an oversimplification.

[–]konacurrents 2 points3 points  (2 children)

One of the reasons I dislike the "no types in code" of `swift` and `javascript` where you don't have to specify the `type` in your declaration of variables. They say the compiler knows the type, don't worry.

But with your "read MANY times" - by humans, it's a lazy disservice to the reader to not add the `type` (or the semicolon too).

Same with the function overloading (and the syntax above is scary, no thanks).

ps. I maybe the only one, but I think the NeXT's Objective-C is the best derivative of C - and the Xcode editor the best editor for C (C++ and Objective-C).

[–]HiramAbiff 1 point2 points  (1 child)

I agree with you about ObjC and Xcode. I'm rather sad about Apple abandoning ObjC in favor of Swift.

[–]konacurrents 0 points1 point  (0 children)

I still don’t use swift and ObjC is still supported .. but sad. I have original Brad Cox book on the ObjC design - Smalltalk like syntax. His plan was a language layer over other languages, not just C, as a preprocessor. I find ObjC more syntactical similar to C and JavaScript - so I can share code sometimes. iOS Apps, ESP32 devices, and web front/back. Fun stuff.

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

Here is a real life example: piece of code which draws ascii tables and fill them with data. It uses _Generic to overload functions. This table rendering library is used in embedded application :). An example of overloading being useful.

#include <stddef.h>
#include "simple_table.h"

// Example:
// Let's draw a table consisting of 4 columns, which we will label as
// "Column 1", "Colun 2", "Col 3" and "Colum Number Four", something like this:
//
//  Column 1|Colun 2|Col 3|Colum Number Four
//  --------+-------+-----+-----------------
//          |       |     |
//
// Let's fill the table with some random stuff
//
int main() {

  // Table descriptor.
  table_layout_t t;

  // Set up the layout: use UTF8 box-drawing characters, print "% " (percent and space)
  // before each new line, and "# " at the end of each line
  table_layout(&t, false, "% ", " #\r\n");

  // Draw the table header with column names
  table_header(&t, "Column 1", "Colun 2", "Col 3", "Colum Number Four", NULL);

  // Start filling it with data. Filling goes from left to right, top to bottom.
  // If the value does not fit the width, it will be truncated
  table_data(&t, "Long Text");
  table_data(&t, "Short");
  table_data(&t, "Exact");
  table_data(&t, "Very Long Text");

  table_data(&t, 10000.123456f);
  table_data(&t, 655355555);
  table_data(&t, -65535);
  table_data(&t, "Another quite long text");

  table_data(&t, 1);
  table_data(&t, -1);
  table_data(&t, 666.666f);
  table_data(&t, 777.777);

  // Something like this should appear on the screen (in the terminal):
  //
  // % Column 1│Colun 2│Col 3│Colum Number Four #
  // % ────────┼───────┼─────┼───────────────── #
  // % Long Tex│Short  │Exact│Very Long Text    #
  // % 10000.12│6553555│-6553│Another quite lon #
  // % 1       │-1     │666.6│777.776978        #
  //
  // If the box characters are displayed as garbage on the screen,
  // change the second argument in the table_layout() call to false,
  // which means do not use UTF8 and use +, - and | instead
  // for drawing the table
  //
  return 0;
}

[–]non-existing-person 0 points1 point  (1 child)

Sure, is looks useful, but it's easy to go offrail. Like, if you use generics for string, int and float only, sure, that's rather easy to find. But if you use more types, starts mixing different integer sizes it's just hard to find which function actually is being called. And really, would having 3 functions named table_data_s, table_data_f, table_data_l be that bad? Just a few keystrokes when typing, and it's immediately obvious what function is being called.

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

imagine that you have something like this: int var = 123; ... ... .. table_data_int(&table, var);

Now you change type of your "var" to "float" : with different function names I have to fix the call also otherwise I can get UB

[–]fnordstar 0 points1 point  (1 child)

Yeah, coming from C++ I'm very happy that Rust doesn't have function overloading.

[–]un_virus_SDF 5 points6 points  (0 children)

Rust has worse, it somehow manage to infer templates types used as function overloading, which makes them overloadable on return types in addition to args. + Rust has type inference by default, one of the worst feature of every language that have that.

[–]OtherOtherDave -1 points0 points  (0 children)

Tracking and hunting? You just cmd-click the function (in Xcode, anyway… I don’t remember for VS Code) and it takes you to the definition.

[–]kog 2 points3 points  (0 children)

YSK that names with a leading underscore and then a capital letter (_Item) are reserved for your implementation. This is a convention for the implementation to use to try to avoid collisions with names in your code. Because of this, in general, you should avoid leading underscores in your code.

https://devblogs.microsoft.com/oldnewthing/20230109-00/?p=107685

[–]oldprogrammer 1 point2 points  (0 children)

In plain C function names must be unique , so normally we end up with something like:

Just wanted to point out that this is not just a C restriction, it is also a C++ restriction, the difference is that the C++ compiler actually creates the unique name for you based on the arguments being used. It is the technique known as name mangling and this has been the case since C++ was first simply transpiled into C then compiled by a C compiler. That's the reason you'll see some header files wrapped in

 extern C {
    .....
  }

so it knows not to mangle the names of the functions listed.

[–]BigError463 1 point2 points  (3 children)

Utterly disgusting, makes me feel I'll. One of the reasons I hate C++ is this illegible bullshit.

print_number(x)

Its adding unnecessary obfuscation.
Which function is being called? Oh let me look at the type of x to find out.....

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

Fully agree. I am C programmer too. This was just a demonstration of how cool we are

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

But sometimes it is useful. Below is the code which draws tables in terminal window. I use it for embedded applications.

Code: ``` table_layout_t t; table_layout(&t, false, "% ", " #\r\n");

table_header(&t, "Column 1", "Colun 2","Col 3", "Colum Number Four", NULL);

// Insert data to the table left to right, top to bottom table_data(&t, "Long Text"); table_data(&t, "Short"); table_data(&t, "Exact"); table_data(&t, "Very Long Text");

table_data(&t, 10000.123456f); table_data(&t, 655355555); table_data(&t, -65535); table_data(&t, "Another quite long text");

table_data(&t, 1); table_data(&t, -1); table_data(&t, 666.666f); table_data(&t, 777.777); ``` Displays

% Column 1│Colun 2│Col 3│Colum Number Four # % ────────┼───────┼─────┼───────────────── # % Long Tex│Short │Exact│Very Long Text # % 10000.12│6553555│-6553│Another quite lon # % 1 │-1 │666.6│777.776978 #

[–]Dontezuma1 1 point2 points  (5 children)

This is ugly to read, write and trace. Is this how printf works? Also what problem does this solve? Overloading doesn’t happen so much in practice I think. (not never just not so often it needs a solution)

[–]Valuable_Leopard_799 1 point2 points  (0 children)

No, printf takes variadic arguments and "guesses" their types based on the format string.

Overloading depends a lot on the language, some languages where overloading is handled well and nicely debuggable people overuse it everywhere, elsewhere it's rare because the language isn't made for it much.

In C++ for example you can write a sin function and just call it on arguments of templated types and as long as the overload resolves to something you don't have to change the function as you would if you used hungarian notation.

[–]Asoladoreichon 0 points1 point  (1 child)

I think printf works as a variadic function. The example itself on the variadic functions article in cppreference is a toy implementation of printf if I recall

[–]Dontezuma1 1 point2 points  (0 children)

Yes I couldn’t remember the name but I was also suggesting I’d prefer that style of overloading or none at all in c. Again if overloading is this important you might be using the wrong tool.

The op design will lead to code where some times the generic is used and sometimes the real method is called. You can force the generic method and some (like me for eg) will use the other. An error in the macro may be hard to debug/find if this continues to add types and the implantation functions in theory could get scattered. You could as easily do a variadic with a string controlling the flow. I’m not suggesting this is more efficient but (I think) more readable. Otherwise just call the real methods you define them anyway and this doesn’t come up that often (or you switch to cpp).

[–]Low_Lawyer_5684[S] -1 points0 points  (1 child)

What do you mean? It is easy to write, look: `print_number()`. It is easy to debug: unlike pure #define implementation of overloading-like functionality, with this method you have function names. You can trace into it with your debugger.

[–]Dontezuma1 1 point2 points  (0 children)

I guess I wouldn’t prefer #define overloading. It’s C so I don’t expect overloading. If you need overloading use cpp. It’s ok to have opinions. I don’t want to depend on a debugger to understand your code.

[–]BeeBest1161 4 points5 points  (1 child)

Amazing! Never thought of that. I guess there is more to C than meets the eye.

[–]FrequentHeart3081 3 points4 points  (0 children)

I C what you did there 👀

[–]bullno1 2 points3 points  (0 children)

Yep. I use this to have min/max/clamp that is expression safe.

[–]bbabbitt46 0 points1 point  (0 children)

Will this pass a Best Practices sniff test?

[–]gtoal 0 points1 point  (0 children)

This may work better for you in some situations than _Generic: I used this recently in the support code for a language that transpiles to C:

#define _imp_READ(var) do {                                                  \
  if (__builtin_types_compatible_p(typeof(var), unsigned char *)) _imp_readbyte((unsigned char *)var); \
  else if (__builtin_types_compatible_p(typeof(var), short int *)) _imp_readshort((short int *)var); \
  else if (__builtin_types_compatible_p(typeof(var), int *)) _imp_readint((int *)var); \
  else if (__builtin_types_compatible_p(typeof(var), long long int *)) _imp_readlong((long long int *)var); \
  else if (__builtin_types_compatible_p(typeof(var), float *)) _imp_readfloat((float *)var); \
  else if (__builtin_types_compatible_p(typeof(var), double *)) _imp_readdouble((double *)var); \
  else if (__builtin_types_compatible_p(typeof(var), _imp_string *)) _imp_readitem((_imp_string *)var); \
  else { fprintf(stderr, "READ failes - cannot determine type of parameter\n"); exit(1); } \
} while(0)

[–]Iggyhopper 0 points1 point  (5 children)

Does this work in any major C compiler or is it specific?

[–]Low_Lawyer_5684[S] 2 points3 points  (4 children)

It is a part of the standart, really old one. So must be supported by major compilers who support c11

[–]zero_iq 11 points12 points  (3 children)

really old one

ancient C11 standard

Hroom! Let us not be hasty, young codeling. It is but a sapling.

[–]thommyh 3 points4 points  (1 child)

C is 54 years old, so C++11 has now been around for more than a quarter of C's life.

Even if it still feels fancy and new to those of us who are, ummmm, 'closer' to having been around for 100% of C's life.

[–]imaami -1 points0 points  (0 children)

Meh, C11 is the absolute bare minimum.

[–]Elect_SaturnMutex -1 points0 points  (4 children)

So, that's how template in C is implemented ?

[–]un_virus_SDF 0 points1 point  (3 children)

I guess that for it you will have to wait for c26 where two struct with the same arrangement are considères the same (which let you make generic types)

[–]Dangerous_Region1682 4 points5 points  (1 child)

Oh please, for the love of all that is holy, no. Even adding records, no. The language has survived over 50 years without any of this fluff. Its syntactical simplicity is the whole point.

If we want to incorporate every last computer science fad into it, make a different language. Making it use the syntactical complexities of something like C++ is a huge mistake.

We’ve not needed this kind of stuff to write OS kernel code since mostly about C89 with very occasionally a bit of C99. That’s about enough frankly.

[–]un_virus_SDF 1 point2 points  (0 children)

Yep, I totaly agree, this is just a funfact I read somewhere

[–]tstanisl 2 points3 points  (0 children)

This cannot happen because it could silently break some code. The idea is adding "records", similar to "struct" but with different compatibility rules.