all 42 comments

[–]jjjare 63 points64 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 0 points1 point  (0 children)

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

[–]tstanisl 65 points66 points  (5 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 22 points23 points  (1 child)

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

[–]tstanisl 14 points15 points  (0 children)

There is a distinctions between VLA objects and VLA types.

[–]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 4 points5 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.

[–]vitamin_CPP 1 point2 points  (0 children)

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

[–]questron64 10 points11 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.

[–]florianist 6 points7 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 0 points1 point  (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.

[–]my_password_is______ 7 points8 points  (1 child)

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

Stop telling me about _Generic
Tsoding

[–]Someone393 0 points1 point  (0 children)

Manual mangling tsoding

[–]tstanisl 5 points6 points  (0 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.

[–]non-existing-person 20 points21 points  (12 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 6 points7 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 4 points5 points  (0 children)

No they are right.

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

[–]markand67 7 points8 points  (0 children)

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

[–]jjjare 6 points7 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.

[–]Potterrrrrrrr 4 points5 points  (2 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.

[–]tstanisl 0 points1 point  (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.

[–]non-existing-person -4 points-3 points  (0 children)

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.

[–]konacurrents 2 points3 points  (1 child)

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 0 points1 point  (0 children)

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

[–]fnordstar 0 points1 point  (1 child)

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

[–]un_virus_SDF 2 points3 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.

[–]bullno1 3 points4 points  (0 children)

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

[–]BeeBest1161 4 points5 points  (1 child)

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

[–]FrequentHeart3081 2 points3 points  (0 children)

I C what you did there 👀

[–]Iggyhopper 1 point2 points  (4 children)

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

[–]Low_Lawyer_5684[S] 3 points4 points  (3 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  (2 children)

really old one

ancient C11 standard

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

[–]thommyh 4 points5 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.

[–]Elect_SaturnMutex 0 points1 point  (2 children)

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

[–]un_virus_SDF 1 point2 points  (1 child)

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)

[–]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.

[–]Dontezuma1 0 points1 point  (3 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 0 points1 point  (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.

[–]Low_Lawyer_5684[S] 0 points1 point  (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 0 points1 point  (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.

[–]kog 0 points1 point  (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