There is one common pattern which is required in many use cases when working and representing ASTs - having a `parent` node, or having access to parents and children. This complicates ownership implementation.
Additonally, is essential to provide an API to transform trees or even construct new ones using immutable ASTs like implemented in many compilers eg .NET Roslin or typescript TSC.
Roslyn specifically does not include parent pointers in its immutable AST nodes. It must do this in order to share those nodes between different versions of the tree.
It only introduces parent pointers in a convenience wrapper layer, built on-demand as you traverse the tree - you could equivalently just pass the parent down as an argument to your tree walking functions.
(The ownership problem of parent pointers also goes away when you use the arena allocation approach that the post arrives at.)
A very effective solution for that is a well-configured shell. IF you summarize the state of the repo in the prompt, it is always visible while typing a command.
Sometimes, you have to make a complex feature or fix. You can first make a prototype of the code or proof of concept that barely works. Then you can see the gap that remains to make the change production ready and the implications of your change. That involves fixing regressions in the test suite caused by your changes.
Exceptions are a very useful tool. In your example, the main program logic is buried in error handling which makes it more difficult to read the code and the code becomes more complex, leading to more bugs. In many cases, it's preferable to have a single error handler centralized in one place, out of line of the main logic. This makes the code more readable, reduces complexity and duplication.
I find today's C++ extremely challenging to pick up speed for someone coming from let's say, Java. Smart pointers, pointer/references, rvalue reference, copy/move semantics, (perfect) forwarding, constructors, and how all that interacts with templates. It's just so unwieldy complex. It takes me hours to write something that would take less than a minute in Java even though I have experience with plain C. The worst thing is that after it finally compiles I feel I can't be sure if I've followed all the rules and best practices correctly or if it's going to blow up spectacularly and potentially unsafely at runtime. The fact that a book like "Effective Modern C++" is needed with all those traps and foot guns to be aware of is ludicrous.
And don't get me wrong - I think C++ is the most powerful language out there on the right hands - it just feels to require a lifetime of learning to became productive on it.
Yeah, I love C++ as a hobbyist programmer but will readily admit I basically use it like C but with the added convenience of strings & classes. Hardly how you're supposed to use it these days, but this is fine for my toy projects. But I can't imagine using the language professionally where you've got to be aware of best practices, modern enhancements, etc. Seems overwhelming!
coming from java you probably don't need all that stuff (and you should already know about constructors). you really need to understand the differences between call/return by value and by reference, and RAII - the rest can be left to the library writers.
I'm always saddened by the distinction between "user level C++" and "library level C++", but I hope it's gotten better over time.
I remember once trying to write my own shared_ptr clone after the release of C++11, and having to learn as I went about why reference collapsing was invented was very, very discouraging.
i think the distinction exists in most languages - in the case of libraries you are trying to make things easy for callers of your code who's motivations you cannot understand; in the case of application code, you probably do have a pretty good idea of how/why people might call it or try to extend/maintain it.
> I find today's C++ extremely challenging to pick up speed for someone coming from let's say, Java. Smart pointers, pointer/references, rvalue reference, copy/move semantics, (perfect) forwarding, constructors, and how all that interacts with templates. It's just so unwieldy complex.
The main difference between Java and C++ in the features you listed boils down to a key difference between Java and C++: object ownership and lifecycle management.
Java's "let's just heap allocate the world and let the JVM sort itself out" allows Java developers to be oblivious to the need to actually think about the life cycle of any object at all. That's fine for some uses, and definitely most of Java's uses, but it's also something that prevents Java from even being considered an option in performance-minded applications. It also gets Java developers to develop incomplete mental models of how computers work. For example, a Java developer who does not understand pointers/references is also a Java developer who fails to understand Java's value and reference types. A Java developer who doesn't understand constructors has also more pressing matters to concern himself with.
In C++, there's a conscientious effort to ensure developers have full control over the life cycle of each and every single object ever instantiated throughout an app session. Smart pointers were added to provide clear semantics on how heap allocated objects should be owned and shared. Move semantics were added because developers want to transfer ownership of resources instead of having to deep copy objects around. These features are aimed at performance and memory safety in a language that by design allows developers to handle low-level details if they want to.
Then there's the backwards compatibility. Perfect forwarding is basically syntactic sugar to get rvalue references to work as expected.
> The worst thing is that after it finally compiles I feel I can't be sure if I've followed all the rules and best practices correctly or if it's going to blow up spectacularly and potentially unsafely at runtime.
The same goes for any programming language. You mentioned Java. I know people who work at an unicorn whose hiring process consists of putting together a Java web service, and they outright rejects people who present a project that hasn't been onboarded onto PMD or SpotBugs. We're talking about companies who hire experts and enforce code reviews, and they still enforce the use of linters and static code analyzers.
When I mention constructors I am talking about initializer lists and all the subtleties and special cases around them.
Basically most of the complexity I mention comes from memory management. It's just a lot of details to know about and keep in your head. It's the price to pay for the flexibility and power.
In Java you need to understand references and object lifecycles or you get memory leaks and slow applications.
Actually, I work on a performance sensitive application and it's written in Java and it's a good choice for it, since the performance comes from algorithmic complexity, mostly from asymptotic complexity. A C++ rewrite wouldn't significantly impact the performance of the application.
> Basically most of the complexity I mention comes from memory management. It's just a lot of details to know about and keep in your head. It's the price to pay for the flexibility and power.
That's basically the point: your complain is not really about C++. You're complaining about a computational problem that some programming languages abstract away with a heavy performance tradeoff. Programming languages that allow developers to manage memory will also force developers to think through memory management and object life cycles. This is not a problem caused by or specific to C++. That's the natural consequence of programming computers.
> Actually, I work on a performance sensitive application and it's written in Java and it's a good choice for it, since the performance comes from algorithmic complexity, mostly from asymptotic complexity. A C++ rewrite wouldn't significantly impact the performance of the application.
I'm sorry for being blunt, but if you think performance-sensitive applications stay at the algorithmic complexity level, you are definitely not dealing with a performance-sensitive application. You're just not writing naive code.
To go straight to the point, performance-sensitive applications are applications where low-level details, such as if memory is allocated at all, if an object is copied or not, and if some data is kept in cache, represent critical performance regressions. We're talking about details beyond cache locality, and keeping that cache hot is a critical feature. Algorithms alone don't cut it.
I recommend checking out the talk from Timur Doumler on low-latency C++ to see what I'm referring.
I know very well what it means to care about a memory allocation cost or an atomic operation in a fast path. In some applications though, that is not the dominating performance factor, instead the algorithm dominates. For example, with C++ an manual memory management our application could take something like 58 minutes to run while in Java it would take 60 minutes. The impact of the low level management is entirely insignificant for performance in the big picture in these cases.
The performance comes from the big O complexity. Memory management is a constant factor in the algorithm.
> We're talking about companies who hire experts and enforce code reviews, and they still enforce the use of linters and static code analyzers.
I'm not sure I'm following, are you suggesting that experts don't need static analysers or linters? Or that enforcing linters is not in and by itself a good thing?
If a static analyzer is sound, which is something that can be mathematically proven (formal method), will find ALL existing issues plus some false positives if it's not complete (which is almost always the case).
Fuel taxes don't address the same issue. Availability of chargers is a blocker for EV adoption. The measure directly helps with that problem, while fuel taxes is a more indirect push. In other words, even if you are heavily pushed by fuel taxes, if you have a blocking barrier with charging infrastructure, buying an EV won't be feasible to begin with.
Additonally, is essential to provide an API to transform trees or even construct new ones using immutable ASTs like implemented in many compilers eg .NET Roslin or typescript TSC.