C_std: Implementation of C++ standard libraries in C(github.com)
github.com
C_std: Implementation of C++ standard libraries in C
https://github.com/KaisenAmin/c_std
11 comments
That's a huge pain point, but I'm curious! What would be a good approach in your opinion? I'm not questioning the exit/print error and carry on, that's plainly wrong.
When working on private projects/libs, my functions usually return an enum error type, and do all data handling - be it arguments or returns - through pointers passed as args; I mean, I think that's the usual convention, but you clearly have way more experience, so I thought about asking! :)
Looking at you, syscall!
Looking at you, syscall!
On Windows, this problem is already solved well. Return 32-bit integers which conform to that spec: https://learn.microsoft.com/en-us/openspecs/windows_protocol... Possible to pack OS errors, third party library specific errors, and other classes of errors into a single value.
Technically, you can implement that spec for all OSes. I did a few times, defining vendor-specific facilities for Linux components I used, like xwindows, drm/kms, etc..
Technically, you can implement that spec for all OSes. I did a few times, defining vendor-specific facilities for Linux components I used, like xwindows, drm/kms, etc..
How do you return an error that communicates "missing quote on line 42 character 200"? A much better solution is to use std::expect (or something home baked if not available) and make the error type something that holds an actual context. HRESULT is a remnant of the past, not something you should replicate in your own code if you have the choice.
https://en.cppreference.com/w/cpp/utility/expected
https://en.cppreference.com/w/cpp/utility/expected
> How do you return an error that communicates "missing quote on line 42 character 200"?
For that use case you gonna need some other piece of data besides the error code. Maybe another type like json_error_t in Jansson library. Or maybe a special strings, like the ones returned by sqlite3_errmsg() in SQLite library.
> not something you should replicate in your own code if you have the choice
I believe HRESULT, despite rather old, is still the best error handling strategy overall.
Language-specific solutions like std::expected don’t work because all complicated software is written in multiple programing languages. For example, the entire ML ecosystem uses Python. 32-bit integers and strings are as language agnostic as you can possibly get.
Enums don’t work because most non-trivial libraries have an unbounded number of possible error conditions. You made a parser library for some format, defined an enum containing all possible failures of the parser, but then a user tried to parse a file he doesn’t have read access to. The failures need to include disk I/O errors. Another user gonna parse their source file from a mounted network share, the failures returned from your parser now need to include TCP/IP errors.
For that use case you gonna need some other piece of data besides the error code. Maybe another type like json_error_t in Jansson library. Or maybe a special strings, like the ones returned by sqlite3_errmsg() in SQLite library.
> not something you should replicate in your own code if you have the choice
I believe HRESULT, despite rather old, is still the best error handling strategy overall.
Language-specific solutions like std::expected don’t work because all complicated software is written in multiple programing languages. For example, the entire ML ecosystem uses Python. 32-bit integers and strings are as language agnostic as you can possibly get.
Enums don’t work because most non-trivial libraries have an unbounded number of possible error conditions. You made a parser library for some format, defined an enum containing all possible failures of the parser, but then a user tried to parse a file he doesn’t have read access to. The failures need to include disk I/O errors. Another user gonna parse their source file from a mounted network share, the failures returned from your parser now need to include TCP/IP errors.
Typically, you have a parser handle and can use that to query it about various aspects of its state. Here it would include line number, character offset, and byte offset. The type of error can be encoded in the error return value.
oh dear - i remember teaching people on COM training courses about HRESULTs - never again, please!
Unfortunately WinDev has decided all modern Windows APIs are based on COM.
They aren't alone, Apple's XPC, Android and Fuchsia's Binder, and even Linux D-DBus, aren't much different.
They have much better tooling though, something that Microsoft folks keep ignoring, so I understand where the pain comes from.
They aren't alone, Apple's XPC, Android and Fuchsia's Binder, and even Linux D-DBus, aren't much different.
They have much better tooling though, something that Microsoft folks keep ignoring, so I understand where the pain comes from.
well, i guess in C you should return a value indicating the error probably via a struct that contains the error and the success data. C programmers seem to have a terrible aversion to returning structs (possibly they don't realise that you can do it?) where in other value-based languages (e.g. c++, go) it is very common.
of course, there are reasons for mucking around with pointers. but down that route lies pointers-to-pointers, and eventually madness.
of course, there are reasons for mucking around with pointers. but down that route lies pointers-to-pointers, and eventually madness.
There are various projects posted here and on other platforms which do this kind of thing (DSA stuff in C).
What baffles me is the lack of any tests here. I understand writing tests is boring, and I understand writing tests in C is the least fun writing tests could be, but: I was thinking how neat this is and how I was going to use this for my own projects, until I saw it has no tests.
C is one of the few languages where, unless you're formally verifying it on paper, you need static analysers, dynamic analysers, and unit tests, all at the same time, to make sure your programs pass the bare minimum stability required to be used.
What baffles me is the lack of any tests here. I understand writing tests is boring, and I understand writing tests in C is the least fun writing tests could be, but: I was thinking how neat this is and how I was going to use this for my own projects, until I saw it has no tests.
C is one of the few languages where, unless you're formally verifying it on paper, you need static analysers, dynamic analysers, and unit tests, all at the same time, to make sure your programs pass the bare minimum stability required to be used.
I don't know if the author is looking at this comment section but small issue:
> Array: Implements a dynamic array similar to std::array in C++.
should read "static array". std::array requires you to state the size.
C++ std::arrays are pretty much just uniformly typed tuple structures, not dynamic arrays.
In fact you can use them directly on the stack with no memory allocations whatsoever.
> Array: Implements a dynamic array similar to std::array in C++.
should read "static array". std::array requires you to state the size.
C++ std::arrays are pretty much just uniformly typed tuple structures, not dynamic arrays.
In fact you can use them directly on the stack with no memory allocations whatsoever.
Erm, actually, all STL implementations I've seen use a simple C-style array under the hood to store `std::array` data, not a much more complex tuple. But it doesn't matter much in the context of the question: the whole point of `std::array` is to provide fixed inplace storage for the data, basically just a safe version of a C-array. Implementing it on top of dynamically sized vector, like the author did, is... just missing the whole idea.
This thing was the first I noticed while looking at the sources... And I don't want to look any more further now.
I think that's what parent comment meant by "tuple": a list-like structure that can't be resized.
Dynamically allocated static array might be closer to truth for what they implemented, I think.
The real std::array is static.
But c doesn't really have any way to do static arrays, so c_std has decided to implement a dynamic array with an api similar to the std::array.
The documentation is correct regarding what c_std has actually implemented. And it's still useful, unlike vector the size is (dynamically) set once at construction and then enforced.
The documentation is correct regarding what c_std has actually implemented. And it's still useful, unlike vector the size is (dynamically) set once at construction and then enforced.
Of course C can do static array:
int array[N];
is one.But not an abstract data structure that can be allocated on the stack.
(Your example can go on the stack. A struct with a final array member of unspecified size can work as an abstract data structure. But you can't have both at once.)
(Your example can go on the stack. A struct with a final array member of unspecified size can work as an abstract data structure. But you can't have both at once.)
Well, std::array is meant to be a direct replacement for what jenadine mentioned, the C style of doing static arrays. I think under the hood it takes over the array initialization syntax as well (or array initialization of objects got added to the standard to support std::array?) and IIRC there are some functions that are simply unavailable to std::array because of its array based construction.
I don't remember exactly what but I have run into it before, I think the iterator implementation is a raw pointer and that breaks the interface expectations a bit?
I don't remember exactly what but I have run into it before, I think the iterator implementation is a raw pointer and that breaks the interface expectations a bit?
While it takes some inspiration from the naming of the C++ standard library, it's done so inconsistently, and the implementation is pretty much nothing like it.
It's just a C framework, and not nearly as high quality as say, Glib.
It's just a C framework, and not nearly as high quality as say, Glib.
+1 for Glib if you are looking for a good C "standard" library.
I hope the author continues to work on this. The C++ standard lib has been worked on for decades so expecting this iteration to be at feature parity is silly. The feedback here will help make it better. It would be nice for C to have this
That's why I support the linux C++ patch.
[0] https://www.phoronix.com/news/CPP-Linux-Kernel-2024-Discuss
[0] https://www.phoronix.com/news/CPP-Linux-Kernel-2024-Discuss
Vector* vector_create(size_t itemSize) {
Vector* vec = (Vector*)malloc(sizeof(Vector));
// ...
return vec;
}
This just immediately strike me as poor code.C is not java, you do not have to "new" up every little structure.
void vector_init(Vector *v, size_t itemSize) {
v->itemSize = itemSize;
// ...
}
Instantly cleaner, more efficient, less memory allocation/handling needed, lets the user control where the Vector is stored.Great work! I always liked the simplicity of C and the neat design of STL. After some quick sneak peeks, I'd suggest allowing the user to set up a custom memory pool and/or allocator (malloc/free). These could be set via additional pointers (with a small overhead) or allowed via ifdefs in compile time so that we can opt for fast or flexible options.
Good luck with future development :)
Good luck with future development :)
Looks very nice! Whenever I write hobby stuff in C, I re-implement (for fun) simple stuff such as a dynamic vector, dynamic string and a key-value map, but nothing to this extent ofc.
The author contacted me via email yesterday (because they saw I'm writing a lot of C++ code on GH) and asked me for a review. This was my somewhat-grumpy somewhat-trollish reply:
Your library demonstrates that C++ is the superior language because it can to template specialization, resulting in better machine code. For example, your std::span implementation needs to store the element size, resulting in a larger structure (24 instead of 16 bytes), more memory memory accesses, costly integer multiplications everywhere.
C++ can omit all this, and can do simple bit shifts instead of multiplications.
There are many more places where you demonstrate C++'s superiority (e.g. it can safely do deep copies with no special code, while your library can't do copies at all, and even if it could, doing so safely/deeply would be extremely cumbersome with C, both for your library code AND for the code calling your library; all a piece of cake in C++).
Oh, and "Implements a dynamic array similar to std::array" .... that's factually wrong. std::array is not a dynamic array. std::vector is. Interestingly, your std::array implementation uses your std::vector, while adding some more runtime overhead. Hey, it's C, it's slower than C++ is what I learn again here!
It's a rather pointless library, unless your point is to demonstrate that C is a bad programming language.
Your library demonstrates that C++ is the superior language because it can to template specialization, resulting in better machine code. For example, your std::span implementation needs to store the element size, resulting in a larger structure (24 instead of 16 bytes), more memory memory accesses, costly integer multiplications everywhere.
C++ can omit all this, and can do simple bit shifts instead of multiplications.
There are many more places where you demonstrate C++'s superiority (e.g. it can safely do deep copies with no special code, while your library can't do copies at all, and even if it could, doing so safely/deeply would be extremely cumbersome with C, both for your library code AND for the code calling your library; all a piece of cake in C++).
Oh, and "Implements a dynamic array similar to std::array" .... that's factually wrong. std::array is not a dynamic array. std::vector is. Interestingly, your std::array implementation uses your std::vector, while adding some more runtime overhead. Hey, it's C, it's slower than C++ is what I learn again here!
It's a rather pointless library, unless your point is to demonstrate that C is a bad programming language.
>C++ can omit all this, and can do simple bit shifts instead of multiplications.
Irrelevant. We need good lib in C
Irrelevant. We need good lib in C
The claim is more that the C++ std is designed to take advantage of C++ language features. Mimicking the same API in C isn't a good lib, the C API shape would need to be different.
Who are "we" and what's wrong with all existing options?
Existing options like? Using 3rd party libs for basic data structures?
The tuple implementation is also nasty in how much memory in allocates. It's the only one I looked at, to be honest.
Do you have anything good to say about it?
Maybe I could say something good about some aspects of their coding style, but that would only distract from my main point that I find it pointless to imitate a C++ API in C, when that API is modeled carefully to take advantage of C++ features, and you lose all of that in C. (Not only that - their C API is designed in a way that adds overhead even where none would be necessary in C, by allocating all structs on the heap.)
There are lots of plain C container libraries which are probably suited better for C, if you really must use C, or prefer C for whatever reason that escapes my imagination.
There are lots of plain C container libraries which are probably suited better for C, if you really must use C, or prefer C for whatever reason that escapes my imagination.
The 1990's were looking so great for C++ adoption on desktop, we had Mac OS (pity about Object Pascal, but at least there was PowerPlant), OS/2 (with CSet++, and OWL), MS-DOS (with Turbo Vision), Windows (with OWL, VCL, MFC, ATL), BeOS (with its Kits), Epoch/Symbian, Windows CE (with MFC),...
And then rise of FOSS happened, with the original GNU contribution guidelines asserting only C and Lisp as the all mighty languages for the GNU ecosystem to build upon.
And then rise of FOSS happened, with the original GNU contribution guidelines asserting only C and Lisp as the all mighty languages for the GNU ecosystem to build upon.
When someone queried Harley-Davidson bikers as to why they don't socialize with youngsters who are avid about the latest racing motorcycles, their response was, "Why should we bother? Every year, they have something new.
--
All jokes aside, check out these cool quotes about C++ from Linus Torvalds [0] and Ken Thompson [1] ;)
[0] https://en.wikiquote.org/wiki/Linus_Torvalds
[1] https://en.wikiquote.org/wiki/Ken_Thompson
--
All jokes aside, check out these cool quotes about C++ from Linus Torvalds [0] and Ken Thompson [1] ;)
[0] https://en.wikiquote.org/wiki/Linus_Torvalds
[1] https://en.wikiquote.org/wiki/Ken_Thompson
Yeah, Linus is a funny guy, as Rust allows one to be as creative as with C++, but apparently he doesn't have an issue with that.
Also maybe Harley shouldn't adopt top e-motorbikes with electronics then, like Livewire.
Also maybe Harley shouldn't adopt top e-motorbikes with electronics then, like Livewire.
The UNIX model won over object approaches. And I think there is a reason: The simplicity of flat memory model, C, a unified file interface etc. removes a lot of complexity and allows composition of diverse components to a working system.
Or in other words: If C++ were actually better for engineering large systems, GNU wouldn't have had a chance.
Or in other words: If C++ were actually better for engineering large systems, GNU wouldn't have had a chance.
> The UNIX model won over object approaches.
That's funny interpretation, because what is this mysterious "UNIX model" and what does it have to do with implementation language?
Also, C++ used to be "C with classes", but has outgrown this single-paradigm thing quite quickly. I do a lot of C++, but I rarely use inheritance and virtual methods. These are not the features that make C++ worthwile for me.
> The simplicity of flat memory model, [C], a unified file interface
This "unified file interface" is a nice theoretical idea, and it leaks many nice things to the real world, but has nothing to do with the implementation language - quite contrary, it allows many different languages to communicate. Similar with "flat memory model" - you can have either language in segmented memory and flat memory. There used to be "far pointers" in both C and C++, and now they're gone, so what.
> If C++ were actually better for engineering large systems, GNU wouldn't have had a chance.
Oh, if only it were that way, if only inferior engineering systems would just lose and disappear. The sad truth is that survival of a language proves little about quality.
If you believe C is better than C++, fine by me, just opinions. You can say "C++ is bad because it's more complex" or "has too many features", I can understand that, or "C++ is confusing because you can overload operators". My features are your bugs, okay. But I can't comprehend your actual arguments because they are orthogonal to the choice of language.
That's funny interpretation, because what is this mysterious "UNIX model" and what does it have to do with implementation language?
Also, C++ used to be "C with classes", but has outgrown this single-paradigm thing quite quickly. I do a lot of C++, but I rarely use inheritance and virtual methods. These are not the features that make C++ worthwile for me.
> The simplicity of flat memory model, [C], a unified file interface
This "unified file interface" is a nice theoretical idea, and it leaks many nice things to the real world, but has nothing to do with the implementation language - quite contrary, it allows many different languages to communicate. Similar with "flat memory model" - you can have either language in segmented memory and flat memory. There used to be "far pointers" in both C and C++, and now they're gone, so what.
> If C++ were actually better for engineering large systems, GNU wouldn't have had a chance.
Oh, if only it were that way, if only inferior engineering systems would just lose and disappear. The sad truth is that survival of a language proves little about quality.
If you believe C is better than C++, fine by me, just opinions. You can say "C++ is bad because it's more complex" or "has too many features", I can understand that, or "C++ is confusing because you can overload operators". My features are your bugs, okay. But I can't comprehend your actual arguments because they are orthogonal to the choice of language.
The comment I was responding to talked about the rise of GNU which allegedly killed better systems that used C++. I argue that GNU won because it was a better system and that it used C and not C++ is one part which made it better. Unified file interfaces are another. You are exactly right that it lets different system communicate, including C++. But this is what makes such interfaces better than - say - an object-oriented interfaces accessed via remote procedure call (and were/are plenty of such systems), which do not interoperate well. Systems built around C++ or with a similar mindset typically tend to have overly complicated interfaces. So yes, one can use simple interfaces with C++. But if you think C++ is a good language, you will likely not define simple interfaces.
Did it?
From where I am standing, it failed in everything except headless computing, with similar input/output devices as a PDP-11.
If it isn't a server, or a some piece of software running on a smart appliance, it hardly matters how much POSIX it is exposed.
By the way, C++ is also UNIX, born and raised by AT&T in their UNIX labs, it is the main reason why all C compiler vendors adopted it in first place, including Stalmman's GCC, as you should clearly be aware.
From where I am standing, it failed in everything except headless computing, with similar input/output devices as a PDP-11.
If it isn't a server, or a some piece of software running on a smart appliance, it hardly matters how much POSIX it is exposed.
By the way, C++ is also UNIX, born and raised by AT&T in their UNIX labs, it is the main reason why all C compiler vendors adopted it in first place, including Stalmman's GCC, as you should clearly be aware.
You are right that you can build a lot of overly complicated crap on top of simpler systems. Android or the modern web are good examples. But in the long run it is usually not the overly complicated crap that prevails.
History shows otherwise, as proven by any usable modern C compiler is now written in C++ like GNU's compiler, so C++ is definitely better for engineering large sytems, GNU did not had a chance either than accept it and move along into modern times.
> If C++ were actually better for engineering large systems, GNU wouldn't have had a chance.
Unfortunely I won't be around to see this happen, but I bet when the UNIX/Linux/BSD founders generation is gone, other OS pushed by big corps will take its place, maybe even taken by younger devs that took the free beer source code and created their new cool startup with an OS partially taken from it, NeXTSTEP/Solaris style.
Something that is already taking shape on IoT space with all those RTOS using MIT/Apache licenses, and very little POSIX/UNIX on them.
> If C++ were actually better for engineering large systems, GNU wouldn't have had a chance.
Unfortunely I won't be around to see this happen, but I bet when the UNIX/Linux/BSD founders generation is gone, other OS pushed by big corps will take its place, maybe even taken by younger devs that took the free beer source code and created their new cool startup with an OS partially taken from it, NeXTSTEP/Solaris style.
Something that is already taking shape on IoT space with all those RTOS using MIT/Apache licenses, and very little POSIX/UNIX on them.
There exist many C compilers written in C, while only relatively few C++ compilers even exist. And while gcc is technically written in C++, large parts still look like C.
OS pushed by big corps definitely have some impact. But this is because big corps can afford to maintain otherwise unmaintainable complex frameworks. As soon as those big corps loose interest, those complex frameworks die, and are replaced by simpler and more reasonable tech.
OS pushed by big corps definitely have some impact. But this is because big corps can afford to maintain otherwise unmaintainable complex frameworks. As soon as those big corps loose interest, those complex frameworks die, and are replaced by simpler and more reasonable tech.
[deleted]
I agree with the criticism that one should not mimick C++ in C.
A good C library would use intrusive data structures, user-controlled memory allocations, not try to replace built-in types (arrays) with inferior alternatives, and not to try to be a kitchen-sink library.
And then it would be obvious such C++ sucks in comparison... ;-)
A good C library would use intrusive data structures, user-controlled memory allocations, not try to replace built-in types (arrays) with inferior alternatives, and not to try to be a kitchen-sink library.
And then it would be obvious such C++ sucks in comparison... ;-)
> intrusive data structures, user-controlled memory allocations
That's par for the course in c++.
> not try to replace built-in types (arrays)
How do you pass around C arrays to avoid the braindead pointer decay? You wrap them in a struct. That's literally all that std::array does.
That's par for the course in c++.
> not try to replace built-in types (arrays)
How do you pass around C arrays to avoid the braindead pointer decay? You wrap them in a struct. That's literally all that std::array does.
You do not need to wrap arrays into structs to prevent decay. You just take the address as you would do for any other type:
https://godbolt.org/z/Y73j8a7Yf
https://godbolt.org/z/Y73j8a7Yf
[deleted]
another general point is that almost all the functions return void. this means that they must have side-effects. i am very far from being an FP maniac, but i do like a bit of purity when i can get it.
also, some comments would be nice.
bottom line - not for me, but then i haven't written any C for over 30 years :-)