Lol at some point just implement a small compiler in C… reflection is kinda a hilarious thing to have like I understand the use case for generics, since it's not that that that hard to do with macros and quite commonly wished for
You definitely should add a detailed post on your "DrC" compiler/interpreter; usecase, scope, techniques used etc. Since this is looking forward to C2y/C29 with some more extensions, i think people will enjoy playing with it in the REPL format.
I agree. Alas I have a day job and my other hobby is going for long hikes. But when the days get shorter and the weather is worse I’ll write some more!
I wrote a summarizing article on type safe container types a while back, but
with some C23 specific changes and a few tweaks to work better for complex types.
It's somewhat of a forced trick nowadays, I think.
I actually thought from the title before I read the article that it was going to use _Generic, as in something like _Generic((item),__typeof__((list)->payload):...) .
It's not worth it. The value of C is in compatibility, availability of compilers, and familiarity.
As soon as you change anything in a breaking way, start requiring your own compiler/transpiler, or introduce new idioms, you end up losing the things that keep C alive.
Users of C either like it exactly the way it is, or have to use a specific C version due to a vendor dependency or compliance.
An upgraded C not approved by the standards body gives you yet another niche language that is not C, but is still burdened with its old flaws.
It would be nice to have something like C which was very much like C and Pascal (not the syntax) but which was basically C except no naked pointers by default etc, fewer footguns, but which would generate C.
mrustc generates (ugly) C from Rust. And doesn't borrow check or otherwise add any bounds checks. It only supports the parts of Rust that the Rust compiler itself needs, but that's quite a lot of it.
I really don't understand why people bring up other languages when one is discussing implementation of some "advanced/tricky/hackish/new" features in C. It is not as if the implementer does not know about the ease of availability in other languages but there is always some set of criteria which prevents switching to a new language.
On a related note, there is a dearth of written/learning material (books etc.) cataloging and explaining design/implementation patterns in C though we know they exist in the tons of industrial-strength codebases out there.
There is also the fact that when you see an implementation in C of some feature from another language you better understand language design pragmatics eg. implementing inheritance and virtual functions in C gives you insight into how they work in C++/Java/C#/etc.
I am working on a language that was C with extensions, transpiled to plain C, but the C syntax is kinda not great to work with (stuff like needing type tables and unbounded lookahead). At the point you clean up the syntax, you're not really C with extensions anymore.
I mean you dont need to make a "language" at all, you can just write a librar for all the stuff you need. This stuff is just syntactic sugar.
For example, when I worked on writing embedded software for autopilots, we had 2 files, common.so and common.h that contained essentially a super robust typing framework. The way it worked is that you defined types through the provided functions at the beginning of your code, and all of the definitions were stored and checked at runtime at the beginning before the main execution loop took effect. So basically taking what a more robust language compiler would do and just moving that processing to the start of execution.
I use something similar for my local llm setup at home, where i can have my agent basically write tools for itself and when the tool fails, the error code makes it pretty easy for the agent to fix.
The difference between me and you is that I do not usually post under C++ articles something negative about C++. Instead I respect that people interested in C++ may have their own reasons. For some reason you find it appropriate to post something negative under each C article.
I could tolerate it if it were at least some interesting criticism,.
Because I find a waste of time to insist in pushing C, with all its flaws, into the industry.
Microsoft, Google and co have done the right decision regarding to what extent C is still required, when we have Typescript for C for about 40 years.
Now if WG14 was actually doing type safe enumerations, sorting out implicit conversations and arrays decaying into pointers only to save typing 4 additionally characters per location, catching up to PL/I strings and arrays, I might be a bit more positive.
One can wait for WG14 to ever deliver that, assuming it will ever happen, or take the easier way out, and use such features on C++ today.
Yes, but please take your general grievances about than Pascal / Modula / Ada did not win elsewhere than to every discussion related to C. Maybe write a nice Modula program and make submission aoub this?
And yet, from my perspective, C - despite having some flaws - is, in fact, a much better language than Cyclone, C++, Ada, or Rust. I think it is rather irrelevant whether the original authors share this opinion or not. C is also not really the same C it was then anyhow. For me it is also not over winning. I use C because it is an extremely efficient tool where I am productive in, and many others are using it productively as well. That most software I use daily is written in C and turns out to be extremely stable, while software written in other languages often annoys me because it is bloated, slow, or not stable, just tends to confirm this.
I've tried doing this for a few years and it just sucks ass. Just use c++ and templates if you need proper generic data structures. C is a defective language.
Never gonna happen. As soon as you kill it off, someone's going to realize they need a very minimal fast "unsafe" language for something, and reinvent C, probably poorly
I think it is a pretty good language. Simple, elegant, performant, stable, and fun to write. Most programs I like for being efficient, non-bloated and reliable turn out to be written in C.
Problems with lead: it's toxic and it accumulates, lead doesn't leave you and builds up over time. It's not just toxic in the sense that too much will kill you, but even small quantities can cause nerve and brain damage, among other things. It is easier for your body to absorb lead than many other elements.
My comment was pointing out that despite lead being known for millennia as a problem in people, it was used for:
* water delivery pipes
* plates and cutlery and cups
* a paint ingredient in everyone's home
* a sealant
* a fuel additive that was spewed into the air by every motor vehical
* a lubricant in a lot of machinery
* a common additive to many additional products all over the place
* a common additive in industrial processes leaving lead residue on products that wouldn't otherwise contain lead.
So the phrase: "underpins most of the technology in the world" somewhat realistically applies to lead - in the early 1900s it was everywhere all the time.
And as i pointed out at the start of this - it is kind of a bad choice to be using all the time in everything since it causes so many problems for people. Humanity has been dramatically reducing the use of lead in everything for a long time now (decades) and will continue to do so for a long time. The bad effects of all the lead use during the industrialization of our species will remain for quite a while.
So my whole point was: just because something was used in the creation of great outcomes, it doesn't make it any good in a fundamental way. I'm saying C is like lead in my analogy.
I don't understand people reinventing macro-based hacks in C to achieve what was achieved in other languages many years ago. Why not using C++, for example? It's available almost everywhere, introducing its usage in an existing C codebase is pretty simple. Sure, C++ has its own downsides, but is it better to create mess with macros in C rather then using exiting language facilities and standard library containers provided by C++?
Having used C++ a lot in the past, I think the mess in C++ is way worse. I also contribute to projects that introduce C++ into a C code base, and I really wish they hadn't done this.
Although there are C features I like that I would need to remove before introducing C++ to a codebase, so this may not always be so simple.
What is mess in C++? Yes, it has some shady parts and is more complex than C, but this complexity provides expressiveness and type safety. And you can always use only features from C++ you find useful.
> also contribute to projects that introduce C++ into a C code base, and I really wish they hadn't done this.
What is problematic in these projects other than unfamiliarity of C++ for developers previously used only C?
How do you make a shallow copy of an object in C++, how do you do the same thing in C? Knowing I can just memcpy _any_ struct in C and have a valid shallow copy is pretty huge.
How do you create a stable ABI in C++? How do you make one in C? In C, knowing that all my exported functions will mostly just work and be stable as long as I don’t change the interface is also huge and allows you to create cross language bindings pretty trivially.
> How do you make a shallow copy of an object in C++
Shallow copies aren't generally possible for classes storing something indirectly, since it violates ownership semantics. That's not how things are done in C++.. Operator = is usually used for making copies, which is optimized to memcpy for POD structures, but for something more complex may perform extra work for doing an actual deep copy.
> How do you create a stable ABI in C++
It's a complex topic. Basic ABI for calling functions is identical to C. But one need to keep in mind, that type layouts and internal implementations of library types (like containers) may differ from implementation to implementation and from version to version.
> knowing that all my exported functions will mostly just work and be stable as long as I don’t change the interface
Nothing prevents you doing this in C++. You can have C-style external interface and use all goods of C++ internally.
You left out the part of my comment where I said “pretty trivially” lol. C++ is mostly a superset of C, which is why I’m not arguing that it’s impossible to do these things in C++. I like C++ too, and think some of the things in there are great. But the simplicity of C is nice. I would often get overwhelmed thinking about all the different constructors in C++ and what I needed to do to ensure that I didn’t accidentally tank performance by copying objects around everywhere (and potentially calling some invisible constructor that did extra work in the copy function). In C, I know when I memcpy it’s just gonna move some bytes around.
Overall, I really don’t care all that much one way or the other. But I can see how people could prefer C to C++. I could also see how people prefer C++. I really really miss function overloading and <string.h> when I use C
You seem to be asking question "why people use hacks in C instead of coding in other languages which are more C++ like". Then you describe what is to be C++ like. Well, maybe the issue is that certain people precisely want to avoid the features you just described. Have you considered that?
You can’t necessarily do that in C either, it depends on if your object is in a container that relies on pointer stability. Intrusive linked lists are common in C and they get broken by this, for example.
Calling memcpy on any random old struct in C is how you get bugs. If the struct contains other pointers: who is in charge of freeing them? What if the struct has internal pointers (a pointer pointing to a subobject of the struct)? You are playing with fire and you know it.
As if C programmers don’t have to be disciplined. Look if you are a C programmer you need to pick good parts of the language to write in as well. A beginner picked gets()? Oh the foes of the young and inexperienced. You picked VLA? Linus would breathe fire unto you.
A difference between C++ programmers and C programmers is that C++ programmers have the awareness to know their language has bad parts to avoid, but C programmers are oblivious to the bad parts until someone uses it, but otherwise sing paeans for the language.
In C, assigning the return value from malloc() to a specific type merely produces a warning:
$ echo -ne "#include <stdlib.h>\nint foo() { return malloc(42); }" | gcc -xc -fsyntax-only -c -
<stdin>: In function ‘foo’:
<stdin>:2:20: warning: returning ‘void *’ from a function with return type ‘int’ makes integer from pointer without a cast [-Wint-conversion]
Whereas in C++ it's a hard error:
$ echo -ne "#include <stdlib.h>\nint foo() { return malloc(42); }" | gcc -xc++ -fsyntax-only -c -
<stdin>: In function ‘int foo()’:
<stdin>:2:26: error: invalid conversion from ‘void*’ to ‘int’ [-fpermissive]
No, this counts as C++ is stupid to me. The memory returned by malloc is UNTYPED. The sole reason why void as a type exists is to convey the notion of no type, so that it needs to be assigned to a type by a programmer. If you mean "byte region, please cast before use" that's 'char *'. Since C++ now forces me to write a cast, it effectively forces me to hide and silence errors.
I do want an error when I violate types, I do not want it for void, because that's the whole meaning of void. C++ manages to make it the worst of both worlds.
Nah. Malloc’s return type is a pointer. The memory pointed to by a malloc call is untyped. But the return type isn’t void. It’s void*, aka a pointer to unknown.
I’m mostly happy with implicit casts from void* to int* or something. But in this code example, we see an implicit cast from void* to int, casting the pointer itself into a number that might not even fit the pointer. This is rarely what you want. I’d much rather explicit casts in this case.
My understanding is that C has a different understanding of the word warning than other languages. Such as in "stern warning" or "warning: the building will collapse if you take that brick out".
This is a philosophical problem. C has false positives to the question: "Is that a program", and some people find that to be idiotic (I don't, this is why I like C). But this becomes really logical, when you have in mind, that the point is that you do not want to have false negatives.
What version is your compiler? As of GCC 14 and clang 15 implicit conversions to int is an error, not merely a warning; and this is without specifying any special conformance or warning modes. I think implicit int conversions have been a constraint violation since at least C99, requiring a diagnostic, though I'm not sure if the switch to an error instead of a warning was prompted by a change in wording in C23 or just a general change in attitude and less concern with failing on pre-existing (but wrong) code.
This was GCC 11. I did try clang 15 and got an error (whereas 14 did not), but my point was that the language spec itself allows this, regardless of any specific compiler, and the original argument (as I understood it) was about the type safety of C and C++ (the language specs) themselves.
I'm not very familiar with the C++ standard, but I don't think the C++ standard requires compilation to fail, either. It has similar language to the C standard. Per C++23 4.1.1p2.3,
> Otherwise, if a program contains a violation of any diagnosable rule or an occurrence of a construct described in this document as “conditionally-supported” when the implementation does not support that construct, a conforming implementation shall issue at least one diagnostic message.
The C++ standard does seem to explicitly require the compiler to reject translation units with a failed static_assert. (The requirements for #error are a little confusing; not sure if a failed #error in a conditional block requires failure.) But I couldn't find a rule for invalid implicit int conversions. Implicit pointer to int conversions are invalid for the same reasons in both C and C++. Basically, AFAICT C++'s type safety in this regard is a matter of historical compiler behavior, and now the major C compilers are adopting that behavior, which they previously had allowed (with a mandatory diagnostic message) for backward compatibility reasons.
In both languages this is a constraint violation, and it is fully at the discretion of the compiler on whether it emits a warning or error (often controllable by compiler flags). For my C compiler (GCC) this is an error.
If C++ adds pattern matching, and it works in practical usage across projects, will the C ecosystem consider adding some kind of pattern matching, perhaps inspired by what comes into C++, perhaps not?
Also, a question for a C expert like you, uecker: In your opinion, does the compound literals feature have complex lifetimes rules or complex semantic rules otherwise?
Personally, I would like to have pattern matching, but I think we need some other features first. I am usually a bit scared about what C++ comes up with.
I am not sure I understand the second question. I do not think compound literals have complex lifetime rules. The lifetime end with the corresponding block, as do other objects in C (one needs to know what a block is, some people confuse this with scope). There is some potential issue which may be surprising when passing them to macros that make use of the statement expression extension, as the lifetime is then limited to the macro body.
I have read up on it, and I do not remember why I arrived at the conclusion that compound literals have complex rules. Their rules are different from C++ and Rust temporaries, but the rules of compound literals are not complex, though their block lifetimes do have to be understood. I have seen at least two C developers being confused about their lifetimes (mostly about when used inside if-statement's condition or in a loop), but most C developers seem to grasp compound literals and their lifetime quickly, and the cppreference.com page is relatively short, which reflects the simplicity of compound literals. It helps that one can understand them as a local variable, local to the specific block that they are used in.
Its rather hard to introduce c++ into legacy c projects. You basically have to decide on a subset of features to use, and then you'll have to explain to the teams why the same looking code now takes 10x more compute to build.
Usually the way we do it here is we honor some interface then rewrite the subsystem in c++.
And then there's the real hurdle and that is getting the c++ idiom correct as it is very easy to just open the floodgates and let everyone write code that looks vastly different.
Not sure why author does it, but I use C over C++ for one reason: I'd rather not have random hidden malloc() scattered in my code. And no, it's not about latency or performance. Once my code takes an yet unexplored part and runs out of 200kB RAM, the will be no way for me to learn about it - running out of memory will bring down both the logger and the radio link, which is the only way to get the logs from a sensor installed twenty meters above ground in a hazardous environment facility. C may be unsafe, but most errors short of "writing to a random memory area" are either well-contained or at least reproducible. Dynamic memory allocation means that anything may crash everything. Manageable if you have a MMU and can contain crashes within isolated heap, but if not - I'd rather not bother. And yea, you can write zero-allocation C++ code, but why bother if most of STL is now unusable? And you get some new and exciting UB modes (seriously, no union aliasing, what the heck?)
Also, template metaprogramming is way more arcane than C macros. And I need compile-time metaprogramming in leu of dynamic allocation. So C macros it is for now (Zig is promising but still not there yet).
These macro tricks (from the article) are not about recreating C++ in C.
C is less opinionated (comp. to C++): it gives you fewer built-in abstractions; does not prescribe which DS dev. should use. It is bring-your-own-data-structures language. Bring whatever you choose.
In C, newer language standard is not always better. Older lang standards support more platforms. There is a certain beauty in writing in an old language while still applying modern coding practices.
I like C. I don't hate other languages. I wouldn't use C for apps that should have been written in C# with WinForms...
I do not get the connection with a VLA in this context, but if you need a VLA, by all means use it. It is basically always better than the alternative.
VLAs are anti C. I also dont even think _Generics should be in the complier. The complier should do minimal processing of syntax in terms of decision making, with only focus on being optimizing code.
Because in the flip side, you get C++ templates, which are turing complete, so you can write entire program syntax that the compliler executies during the complie process.
I absolutely cannot remember the details, but we did typesafe, macro based generic data structures in C in an undergrad class I took ~20 years ago (CMU Operating Systems). The idea has definitely been around, though I don't know if the implantation was the same.
I just want C with classes, like C++ started out as.
What I'd really like is a standards-conformant (C++98/03) compiler that actually enforces the standard properly (or maybe it becomes a new standard), and ONLY allows that version to be used, AND only the classes.
No templates, no exceptions, no standard library (just use a plain libc).
101 comments
[ 4.4 ms ] story [ 24.1 ms ] threadhttps://www.open-std.org/jtc1/sc22/wg14/www/docs/n3531.txt
I also posted your "Adding Reflection to C" at https://news.ycombinator.com/item?id=49964525 and hope HN picks up on it and has a decent discussion.
You definitely should add a detailed post on your "DrC" compiler/interpreter; usecase, scope, techniques used etc. Since this is looking forward to C2y/C29 with some more extensions, i think people will enjoy playing with it in the REPL format.
https://louissven.xyz/article/how_I_do_container_types_in_C....
Feel free to flag/delete if this isn't the place.
It is really nice that you studied both Martin Uecker and Daniel Hooper's techniques and tweaked them to suit your ideas/taste.
Still feels a bit like a party trick, but if it works...
I actually thought from the title before I read the article that it was going to use _Generic, as in something like _Generic((item),__typeof__((list)->payload):...) .
just as was done with C++?
As soon as you change anything in a breaking way, start requiring your own compiler/transpiler, or introduce new idioms, you end up losing the things that keep C alive.
Users of C either like it exactly the way it is, or have to use a specific C version due to a vendor dependency or compliance.
An upgraded C not approved by the standards body gives you yet another niche language that is not C, but is still burdened with its old flaws.
I really don't understand why people bring up other languages when one is discussing implementation of some "advanced/tricky/hackish/new" features in C. It is not as if the implementer does not know about the ease of availability in other languages but there is always some set of criteria which prevents switching to a new language.
On a related note, there is a dearth of written/learning material (books etc.) cataloging and explaining design/implementation patterns in C though we know they exist in the tons of industrial-strength codebases out there.
There is also the fact that when you see an implementation in C of some feature from another language you better understand language design pragmatics eg. implementing inheritance and virtual functions in C gives you insight into how they work in C++/Java/C#/etc.
For example, when I worked on writing embedded software for autopilots, we had 2 files, common.so and common.h that contained essentially a super robust typing framework. The way it worked is that you defined types through the provided functions at the beginning of your code, and all of the definitions were stored and checked at runtime at the beginning before the main execution loop took effect. So basically taking what a more robust language compiler would do and just moving that processing to the start of execution.
I use something similar for my local llm setup at home, where i can have my agent basically write tools for itself and when the tool fails, the error code makes it pretty easy for the agent to fix.
But if you compare some C++ template container to a C macro solution, I also do not find the macro solution to be more complex.
We will keep on agreeing to disagree.
I could tolerate it if it were at least some interesting criticism,.
Microsoft, Google and co have done the right decision regarding to what extent C is still required, when we have Typescript for C for about 40 years.
Now if WG14 was actually doing type safe enumerations, sorting out implicit conversations and arrays decaying into pointers only to save typing 4 additionally characters per location, catching up to PL/I strings and arrays, I might be a bit more positive.
One can wait for WG14 to ever deliver that, assuming it will ever happen, or take the easier way out, and use such features on C++ today.
Likewise by 2002, AT&T also decided C had enough of lifetime and started the Cyclone project, which has a great effect in modern type systems.
They might have not won over C, others will, with projects whose influences trace back to where UNIX and C were born, quite ironically.
Which is much more interesting to post about than Modula-2 in 2026.
Examples: https://godbolt.org/z/hecqs78xs https://godbolt.org/z/YnrcjKrEn
Not perfect, but also not really inferior to C++ in my opinion.
Most elements on periodic table is toxic to human.
My comment was pointing out that despite lead being known for millennia as a problem in people, it was used for:
* water delivery pipes * plates and cutlery and cups * a paint ingredient in everyone's home * a sealant * a fuel additive that was spewed into the air by every motor vehical * a lubricant in a lot of machinery * a common additive to many additional products all over the place * a common additive in industrial processes leaving lead residue on products that wouldn't otherwise contain lead.
So the phrase: "underpins most of the technology in the world" somewhat realistically applies to lead - in the early 1900s it was everywhere all the time.
And as i pointed out at the start of this - it is kind of a bad choice to be using all the time in everything since it causes so many problems for people. Humanity has been dramatically reducing the use of lead in everything for a long time now (decades) and will continue to do so for a long time. The bad effects of all the lead use during the industrialization of our species will remain for quite a while.
So my whole point was: just because something was used in the creation of great outcomes, it doesn't make it any good in a fundamental way. I'm saying C is like lead in my analogy.
Although there are C features I like that I would need to remove before introducing C++ to a codebase, so this may not always be so simple.
What is mess in C++? Yes, it has some shady parts and is more complex than C, but this complexity provides expressiveness and type safety. And you can always use only features from C++ you find useful.
> also contribute to projects that introduce C++ into a C code base, and I really wish they hadn't done this.
What is problematic in these projects other than unfamiliarity of C++ for developers previously used only C?
Just a couple examples:
How do you make a shallow copy of an object in C++, how do you do the same thing in C? Knowing I can just memcpy _any_ struct in C and have a valid shallow copy is pretty huge.
How do you create a stable ABI in C++? How do you make one in C? In C, knowing that all my exported functions will mostly just work and be stable as long as I don’t change the interface is also huge and allows you to create cross language bindings pretty trivially.
Shallow copies aren't generally possible for classes storing something indirectly, since it violates ownership semantics. That's not how things are done in C++.. Operator = is usually used for making copies, which is optimized to memcpy for POD structures, but for something more complex may perform extra work for doing an actual deep copy.
> How do you create a stable ABI in C++
It's a complex topic. Basic ABI for calling functions is identical to C. But one need to keep in mind, that type layouts and internal implementations of library types (like containers) may differ from implementation to implementation and from version to version.
> knowing that all my exported functions will mostly just work and be stable as long as I don’t change the interface
Nothing prevents you doing this in C++. You can have C-style external interface and use all goods of C++ internally.
"That's not how things are done in C++" I like C, because I can just decide for myself how things are done here.
You left out the part of my comment where I said “pretty trivially” lol. C++ is mostly a superset of C, which is why I’m not arguing that it’s impossible to do these things in C++. I like C++ too, and think some of the things in there are great. But the simplicity of C is nice. I would often get overwhelmed thinking about all the different constructors in C++ and what I needed to do to ensure that I didn’t accidentally tank performance by copying objects around everywhere (and potentially calling some invisible constructor that did extra work in the copy function). In C, I know when I memcpy it’s just gonna move some bytes around.
Overall, I really don’t care all that much one way or the other. But I can see how people could prefer C to C++. I could also see how people prefer C++. I really really miss function overloading and <string.h> when I use C
Ah, the old "programmers just need to be more disciplined when using C++".
Yeah, C programmers have never seen that argument before...
A difference between C++ programmers and C programmers is that C++ programmers have the awareness to know their language has bad parts to avoid, but C programmers are oblivious to the bad parts until someone uses it, but otherwise sing paeans for the language.
I do not think C++ is more expressive or type safe.
I do want an error when I violate types, I do not want it for void, because that's the whole meaning of void. C++ manages to make it the worst of both worlds.
I’m mostly happy with implicit casts from void* to int* or something. But in this code example, we see an implicit cast from void* to int, casting the pointer itself into a number that might not even fit the pointer. This is rarely what you want. I’d much rather explicit casts in this case.
My understanding is that C has a different understanding of the word warning than other languages. Such as in "stern warning" or "warning: the building will collapse if you take that brick out".
This is a philosophical problem. C has false positives to the question: "Is that a program", and some people find that to be idiotic (I don't, this is why I like C). But this becomes really logical, when you have in mind, that the point is that you do not want to have false negatives.
> Otherwise, if a program contains a violation of any diagnosable rule or an occurrence of a construct described in this document as “conditionally-supported” when the implementation does not support that construct, a conforming implementation shall issue at least one diagnostic message.
The C++ standard does seem to explicitly require the compiler to reject translation units with a failed static_assert. (The requirements for #error are a little confusing; not sure if a failed #error in a conditional block requires failure.) But I couldn't find a rule for invalid implicit int conversions. Implicit pointer to int conversions are invalid for the same reasons in both C and C++. Basically, AFAICT C++'s type safety in this regard is a matter of historical compiler behavior, and now the major C compilers are adopting that behavior, which they previously had allowed (with a mandatory diagnostic message) for backward compatibility reasons.
Also, a question for a C expert like you, uecker: In your opinion, does the compound literals feature have complex lifetimes rules or complex semantic rules otherwise?
I am not sure I understand the second question. I do not think compound literals have complex lifetime rules. The lifetime end with the corresponding block, as do other objects in C (one needs to know what a block is, some people confuse this with scope). There is some potential issue which may be surprising when passing them to macros that make use of the statement expression extension, as the lifetime is then limited to the macro body.
I have read up on it, and I do not remember why I arrived at the conclusion that compound literals have complex rules. Their rules are different from C++ and Rust temporaries, but the rules of compound literals are not complex, though their block lifetimes do have to be understood. I have seen at least two C developers being confused about their lifetimes (mostly about when used inside if-statement's condition or in a loop), but most C developers seem to grasp compound literals and their lifetime quickly, and the cppreference.com page is relatively short, which reflects the simplicity of compound literals. It helps that one can understand them as a local variable, local to the specific block that they are used in.
In contrast, C++ and Rust temporary lifetimes can vary, and those lifetime rules can confuse developers, like in https://fasterthanli.me/articles/a-rust-match-made-in-hell .
Mojo seems to avoid many of C++'s and Rust's issues on the topic about lifetimes.
Usually the way we do it here is we honor some interface then rewrite the subsystem in c++.
And then there's the real hurdle and that is getting the c++ idiom correct as it is very easy to just open the floodgates and let everyone write code that looks vastly different.
Also, template metaprogramming is way more arcane than C macros. And I need compile-time metaprogramming in leu of dynamic allocation. So C macros it is for now (Zig is promising but still not there yet).
Problem is C++ has many features which I likely won't be able to understand in this lifetime. But using C-style C++ is nuts.
Just the extra of STL is basically the C that C should have been. It's a shame not to have a string type or use brittle macros.
These macro tricks (from the article) are not about recreating C++ in C. C is less opinionated (comp. to C++): it gives you fewer built-in abstractions; does not prescribe which DS dev. should use. It is bring-your-own-data-structures language. Bring whatever you choose. In C, newer language standard is not always better. Older lang standards support more platforms. There is a certain beauty in writing in an old language while still applying modern coding practices.
I like C. I don't hate other languages. I wouldn't use C for apps that should have been written in C# with WinForms...
Because in the flip side, you get C++ templates, which are turing complete, so you can write entire program syntax that the compliler executies during the complie process.
What I'd really like is a standards-conformant (C++98/03) compiler that actually enforces the standard properly (or maybe it becomes a new standard), and ONLY allows that version to be used, AND only the classes.
No templates, no exceptions, no standard library (just use a plain libc).
I write type-safe generic data structures in C - https://news.ycombinator.com/item?id=44425461 - June 2025 (182 comments)
Type-safe generic data structures in C - https://news.ycombinator.com/item?id=26735655 - April 2021 (52 comments)
https://github.com/openbsd/src/blob/master/sys/sys/queue.h