> but absolutely no one is going to switch from C to C++ just for dtors
The decision would be easier if the C subset in C++ would be compatible with modern C standards instead of being a non-standard dialect of C stuck in ca. 1995.
No, but also skip malloc/free until late in the year, and when it comes to heap allocation then don't use example code which allocates and frees single structs, instead introduce concepts like arena allocators to bundle many items with the same max lifetime, pool allocators with generation-counted slots and other memory managements strategies.
This only covers one aspect though (pools indexed by 'generation-counted-index-handles' to solve temporal memory safety - e.g. a runtime solution for use-after-free).
You're missing out on one of the best-integrated and useful features that have been added to a language as an afterthought (C99 designated initialization). Even many moden languages (e.g. Rust, Zig, C++20) don't get close when it comes to data initialization.
In general it would help if you would spend some text on describing what features of C99 are missing in other languages. Giving some code and assume that the reader will figure it out is not very effective.
As far as I can tell, Rust can do what it is in your example (which different syntax of course) except for this particular way of initializing an array.
To me, that seems like a pretty minor syntax issue to that could be added to Rust if there would be a lot of demand for initializing arrays this way.
E.g. notice how here in Rust each nested struct needs a type annotation, even though the compiler could trivially infer the type. Rust also cannot initialize arrays with random access directly, it needs to go through an expression block. Finally Rust requires `..Default::default()`:
I don't have C++ code around, but compared to C99 it has the following restrictions:
- designators must appear in order (a no-go for any non-trivial struct)
- cannot chain designators (e.g. `.a.b.c = 123`)
- doesn't have the random array access syntax via `[index]`
> ...like a pretty minor syntax issue...
...sure, each language only has a handful minor syntax issues, but these papercuts add up to a lot of syntax noise to sift through when compared to the equivalent C99 code.
In Rust you can do "fn new(field: Field) -> Self { Self { field } )"
This is in my experience the most common case of initializers in Rust.
You don't mention one of the features of the Rust syntax, that you only have to specify the field name when you have a variable with the same name. In my experience, that reduces clutter a lot.
I have to admit, the ..Default::default() syntax is pretty ugly.
In theory Rust could do "let x: Foo = _ { field }" and "Foo { field: _ { bar: 1 } }". That doesn't even change the syntax. Its just whether enough people care.
designated initializers are really great and it's really annoying that C++ has such a crappy version of them. I wish there was a way to tell the compiler that the default value of some fields should not necessarily be 0, though it's ergonomic enough to do that anyway with a macro, since repeated values override.
Desctructors are only comparable when you build an OnScopeExit class which calls a user-provided lambda in its destructor which then does the cleanup work - so it's more like a workaround to build a defer feature using C++ features.
The classical case of 'one destructor per class' would require to design the entire code base around classes which comes with plenty of downsides.
> Anyone who writes C should consider using C++ instead
Nah thanks, been there, done that. Switching back to C from C++ about 9 years ago was one of my better decisions in life ;)
> in the real world we need at least polymorphism and operator overloading
Maybe in your real-world ;)
Building your game code around classes with virtual methods has been a bad idea since at least the early 2000s (but both static and dynamic polymorphism is something that Zig can do just fine when needed), and the only important use case for operator overloading in game-dev is vector/matrix math, where Zig is going down a different road (using builting vector types, which maybe one day will be extended with a builtin matrix type - there is some interest in using Zig for GPU code, and at least this use cases will require proper vector/matrix primitives - but not operator overloading).
The optimization work happens in the LLVM backend, so in most cases (and using the same optimization and target settings - which is an important detail, because by default Zig uses more aggressive optimization options than Clang), similar Zig and C code translates to the exact same machine code (when using Clang to build the C code).
The same should be true for any compiled language sitting on top of LLVM btw, not just C vs Zig.
With default build settings it actually might be, because Zig's release mode builds with the equivalent of `-march=native` by default ;)
(disclaimer: not sure if that's actually still the case, last I checked in detail was probably 2 years ago).
Also Zig always builds the entire project as a single compilation unit, which allows more optimization options because the compiler sees all function bodies. The closest equivalent in the C world is LTO, but this is usually also not enabled by default.
Counterpoint: why even use LLMs to create apps when the prompting session is the one universal app which can directly solve any problem a specialized app would solve (yes, it's all extremely silly, but so is the article).
The decision would be easier if the C subset in C++ would be compatible with modern C standards instead of being a non-standard dialect of C stuck in ca. 1995.