- no exceptions
- no recursion
- no malloc()/free() in the inner-loop
You can compile with exceptions enabled, use the STL, but strictly enforce no allocations after initialization. It depends on how strict is the spec you are trying to hit.
It is "C++", but we also follow the same standards. Static memory allocation, no exceptions, no recursion. We don't use templates. We barely use inheritance. It's more like C with classes.
My guess is that you're assuming all user defined types, and maybe even all non-trivial built-in types too, are boxed, meaning they're allocated on the heap when we create them.
That's not the case in C++ (the language in question here) and it's rarely the case in other modern languages because it has terrible performance qualities.
The idea of `become` is to signal "I believe this can be tail recursive" and then the compiler is either going to agree and deliver the optimized machine code, or disagree and your program won't compile, so in neither case have you introduced a stack overflow.
Rust's Drop mechanism throws a small spanner into this, in principle if every function foo makes a Goose, and then in most cases calls foo again, we shouldn't Drop each Goose until the functions return, which is too late, that's now our tail instead of the call. So the `become` feature AIUI will spot this, and Drop that Goose early (or refuse to compile) to support the optimization.
But ... that rewrite can increase the cyclomatic complexity of the code on which they have some hard limits, so perhaps that's why it isn't allowed? And the stack overflow, of course.
I feel like that's the way to go since you don't obscure control flow. I have also been considered adding assertions like TigerBeetle does
https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/TI...
Provocative talk though, it upends one of the pillars of deeply embedded programming, at least from a size perspective.
> no recursion
Does this actually mean no recursion or does it just mean to limit stack use? Because processing a tree, for example, is recursive even if you use an array, for example, instead of the stack to keep track of your progress. The real trick is limiting memory consumption, which requires limiting input size.
- C++ Exceptions Reduce Firmware Code Size, ACCU [1]
- C++ Exceptions for Smaller Firmware, CppCon [2]
The C++ was atrocious. Home-made reference counting that was thread-dangerous, but depending on what kind of object the multi-multi-multi diamond inheritance would use, sometimes it would increment, sometimes it wouldn't. Entire objects made out of weird inheritance chains. Even the naming system was crazy; "pencilFactory" wasn't a factory for making pencils, it was anything that was made by the factory for pencils. Inheritance rather than composition was very clearly the model; if some other object had function you needed, you would inherit from that also. Which led to some object inheriting from the same class a half-dozen times in all.
The multi-inheritance system given weird control by objects on creation defining what kind of objects (from the set of all kinds that they actually were) they could be cast to via a special function, but any time someone wanted one that wasn't on that list they'd just cast to it using C++ anyway. You had to cast, because the functions were all deliberately private - to force you to cast. But not how C++ would expect you to cast, oh no!
Crazy, home made containers that were like Win32 opaque objects; you'd just get a void pointer to the object you wanted, and to get the next one pass that void pointer back in. Obviously trying to copy MS COM with IUnknown and other such home made QueryInterface nonsense, in effect creating their own inheritance system on top of C++.
What I really learned is that it's possible to create systems that maintain years of uptime and keep their frame accuracy even with the most atrocious, utterly insane architecture decisions that make it so clear the original architect was thinking in C the whole time and using C++ to build his own terrible implementation of C++, and THAT'S what he wrote it all in.
Gosh, this was a fun walk down memory lane.
No algorithms or containers, which to me is probably 90% of what is most heavily used of the STL.
So, what exact parts of the STL do you use in your code base? Most be mostly compile time stuff (types, type trait, etc).
C++ is designed to make this pretty easy.
it was painful for me to accept that the most elite programmers i have ever encountered were the ones working in high frequency trading, finance, and mass-producers of 'slop' (adtech, etc.)
i still ache to work in embedded fields, in 8kB constrained environment to write perfectly correct code without a cycle wasted, but i know from (others) experience that embedded software tends to have the worst software developers and software development practices of them all.
Because Rust is allowed (at this sort of distance in time) to reserve new keywords via editions, it's not a problem to invent more, so I generally do prefer new keywords over re-using existing words but I'm sure I'd be interested in reading the pros and cons.
Some large commercial software systems use C++ exceptions, though.
Until recently, pretty much all implementations seemed to have a global mutex on the throw path. With higher and higher core counts, the affordable throw rate in a process was getting surprisingly slow. But the lock is gone in GCC/libstdc++ with glibc. Hopefully the other implementations follow, so that we don't end up with yet another error handling scheme for C++.
Also, serious question: Are they any GUI toolkits that do not use multiple inheritance? Even Java Swing uses multiple inheritance through interfaces. (I guess DotNet does something similar.) Qt has it all over the place.
Actually the only toolkit that I know that sort of copied this style is Nakst's Luigi toolkit (also in C).
Neither really used inheritance and use composition with "message passing" sent to different controls.
I mean .at is great and all, but it's really for the benefit of eliminating undefined behavior and if the program just terminates then you've achieved this. I've seen decoders that just catch the std::out_of_range or even std::exception to handle the remaining bugs in the logic, though.