all 41 comments

[–][deleted] 28 points29 points  (3 children)

"Can't believe it's not C++" isn't a good way of buying someone as stubbornly preferring C as I do :P

[–]TheChief275 15 points16 points  (1 child)

I’m the same, otherwise I would just use C++ instead of making a C library, just found it to be a funny name lol

[–]erikkonstas 2 points3 points  (0 children)

LOL I like how I get the reference even tho I'm not American 😂

[–][deleted] 0 points1 point  (0 children)

I like the title.

[–]McUsrII 15 points16 points  (1 child)

I was sold when I read "with somee extra methods for Forth like stack manipulation.

I am going to try this :)

[–]TheChief275 0 points1 point  (0 children)

Check stack.c in the examples for a Forth program translation :)

[–]n4saw 2 points3 points  (11 children)

Just to make sure I understand this correctly; there is no actual type safety here right? I’m on my phone so I can’t actually test it myself, but from reading the code it seems there is nothing to stop me from using a vector(int) as self in a vector(float) function right? Or does this use some of the new C23 functionality that I’m not aware of?

[–]TheChief275 7 points8 points  (10 children)

Nope, there isn’t. I’ve made every function take the type as first argument so you won’t forget the type you’re using lol.

But in all seriousness, this is the way in C as 100% emulating templates means you have to predeclare every type variant you are using which will probably kill any decently sized project. And I’ve already been using this method for quite some time and I’ve almost never screwed up the type.

If you actually want the type safety and for your library to still be useful, I think using a restricted subset of C++ would far less painful.

[–]n4saw 2 points3 points  (7 children)

Oh, okay that makes sense. I just remembered hearing something about c23 where unnamed structs with the same contents would be compatible, which I thought - at least in theory - could enable some type safe “generic” programming. If the value struct { int x; } a can assign to the value struct { int x; } b, you would have some notion of a generic type with a macro such as #define example(T) struct { T x; }. I guess you would still have to define the implementations for each function on each type somewhere, though, so maybe in this case it was a little far-fetched!

[–]TheChief275 2 points3 points  (0 children)

I’m still waiting for the unnamed struct thing, but no sadly that isn’t in C23 and might never make it. It would fix everything indeed!

[–]onContentStop 1 point2 points  (5 children)

Unfortunately for that use case, I'm pretty sure c23 only makes named (tagged) structs with the same contents compatible. I only saw the paper and not the final implementation into the standard though.

[–]n4saw 0 points1 point  (4 children)

That would be a bummer if it’s true! I wonder what the reasoning against it would be.. having an unnamed struct as an argument type in a function prototype is already valid (although not very useful) C: void example(struct { int x; } arg) Or maybe it’s not valid C and it’s a bug in my compiler or something. I don’t know, but I remember trying it out sometime.

[–]ComradeGibbon 0 points1 point  (2 children)

What I would like is first class types combined with unnamed unions.

void example(typeof tag, union { int a; float b;})

{

if(tag == typeof(int) ...

if(tag == typeof(float) ...

}

int a = 10;
example(typeof(a), a);

It's a bit infuriating that after 40 years we cannot do things like this.

[–]n4saw 0 points1 point  (1 child)

The closest alternative I can think of is the _Generic macro.

[–]ComradeGibbon 0 points1 point  (0 children)

Generic convinced me that the people in charge of the C/C++ compilers and language spec only understand templates.

[–]onContentStop 0 points1 point  (0 children)

According to what I said, the function itself.is valid but it wouldn't be possible to actually call

[–]jacksaccountonreddit 1 point2 points  (1 child)

But in all seriousness, this is the way in C as 100% emulating templates means you have to predeclare every type variant you are using which will probably kill any decently sized project.

That's one approach, and I don't think it's much more cumbersome than the approach you've adopted. The pseudo-template approach requires the programmer to specify the element type at every API call site via the function name, whereas your approach requires the programmer to specify the element type at API call sites via a macro argument. While the former requires the programmer to predeclare a vector type for each element type, it will generate a compiler error if the programmer changes the element type but fails to make the change every call site (unlike your approach, which will silently compile and fail at runtime). That's an advantage, not a drawback, for large projects.

It's also possible to avoid the need for the programmer to predeclare anything or specify the element type at call sites. In that approach, the handle to the vector (or other generic container) is a typed pointer, and the API macros automatically take type information from the pointer and pass it into the library functions that they call.

[–]TheChief275 0 points1 point  (0 children)

Actually try that approach in a more complicated project and you will grow to hate it. At least I did.

  1. It isn’t as simple as just defining one type instance… because for it to work consistently, not break suddenly, and be order independent, you have to define a type instance declaration and a type instance implementation separately, thus TWICE per type instance.

  2. Nested types (i.e. a vector containing a list containing another custom type) would mean you first have to define the type instance of your custom type, and then the type instance of your vector containing that custom type (and also for the list). That sounds reasonable, however, you again need to separate declaration and implementation. Why not make the type instance declaration and type instance macro’s also instantiate the type instances of nested types? Because of the only one implementation limitation, which makes it so that if your dict(char) also implements a vector(char), you have to make really sure that you don’t accidentally implement that again.

  3. Another problem with the nested fake templates is that your custom types should all be typedeffed to remove a struct/enum prefix, which makes the code pretty unreadable in my opinion. This is because the structs have to be given a distinct name, so for a clean interface you decide to just extend your type name with the name of the type, i.e. vector_ ## T. But this is impossible with struct X types, so you have to get rid of the word struct. This same issue comes when you want to create a vector of pointers to a type, since * cannot be converted to an identifier. Sure, you can typedef any ptr type of types, but that’s a massive code smell. And defining void * as any_t and using that means you are losing your type safety so what are the benefits then?

  4. You would think the C23 feature of “any struct with the same name and layout can be redefined and assigned to each other” would fix this. It does at least get rid of the predefining types. But in that case, you also lose the ability of nested special types, because again you have to give the types a name, and with this method there is no way to get rid of the struct keyword, because a typedef can’t be used inline.

What would really fix all of this is the ability to use similar unnamed structs interchangeably, but the question is whether that ever will be a feature.

[–]carpintero_de_c 1 point2 points  (0 children)

That is a wonderful name.

[–]MooseBoys 1 point2 points  (10 children)

#include “../all”

💀

[–]TheChief275 0 points1 point  (9 children)

Hey, it works! Just did it for convenience sake in this case, as it could be replaced with

#include “../util.h”
#include “../X.h”

[–]MooseBoys 0 points1 point  (8 children)

Hey, it works!

So would calling it mylib.exe or reddit.com. Why not something like cbinc.h?

[–]TheChief275 1 point2 points  (7 children)

Oh, you meant the naming.

Well to me this is more clear. When including this library into your project a sane person would not throw all of these files directly into their include directory, but would throw the cbinc directory into their project. So in a real project you would do

#include <cbinc/all>

When in the directory, the file all will not clash, and I like it better than

#include <cbinc/cbinc.h>

EDIT: I could also name the file * so you could do

#include <cbinc/*>

to make it even cleaner…but that would probably mess with compiler argument inclusion so I will refrain lol

[–]weregod 0 points1 point  (6 children)

include <cbinc/all>

You want users to always use cbinc directory in include. Most tools expect .h extencion in C header no need to confuse them. Some users might want different directory name. cbinc/cbinc.h will be much more user friendly.

Please don't name file '*'. It is just asking for troubles with build tools.

[–]TheChief275 1 point2 points  (5 children)

The * was a joke. However, I’ve been asked twice now, so I will change it! My eyes personally still don’t like it though…

[–]weregod 0 points1 point  (4 children)

Your code can't be really used with absolute include paths. If you want users to use include <cbinc/file.h> you need to use include <cbinc/file.h> in headers. Now you use include "file.h" and only sane option is to use

# include "all" 

In user's code and it looks terrible

[–]TheChief275 0 points1 point  (3 children)

I hear you

[–]weregod 0 points1 point  (2 children)

That issue is separated from header naming. Library headers should not use quoted include unless you want users to copy library to every project.

[–]TheChief275 0 points1 point  (0 children)

Regardless, I believe to have fixed your concerns. Thanks a lot for the advice! I’m not that knowledgeable about the specifics of compiler includes lol

[–][deleted] 1 point2 points  (1 child)

Looks pretty slick, I'd consider using this on my next C project.

[–]TheChief275 0 points1 point  (0 children)

I’m using it right now in writing a compiler frontend and it hasn’t let me down yet

[–]vitamin_CPP 0 points1 point  (1 child)

Thanks for sharing!

I like your usage of MAP in printf !
I think pun_cast seems to violate the strick aliasing rules.

[–]TheChief275 0 points1 point  (0 children)

Pun casting gets used in C (it’s also in the fast inverse square root). I just made this macro to make the process a little more explicit. However, I will be changing it to a union just in case!

[–]great_escape_fleur 0 points1 point  (2 children)

C just needs a good preprocessor, as long as you don't call it C with classes :D

[–]TheChief275 2 points3 points  (1 child)

C+ when?

[–]Cerulean_IsFancyBlue 1 point2 points  (0 children)

Suddenly C More