I am a bona fide C++ hater, but that's just because I resent working on it for professional code that needs to work reliably with coworkers I don't entirely trust. For personal projects it's one of the most satisfying coding contexts I've ever worked and it is indeed a blast to have such fine-grained control over execution.
I abandoned the language about a decade ago and I don't see myself looking back. My projects these days need long-term reliability more than anything and rust + cd/ci hits a sweet spot. That said, I do miss the thrill of designing and executing a program that runs in a certain way exactly as I intended and knowing that it was my expertise and insight that allowed this execution. Would I want to work with someone that was driven by that? Hell no! But it is personally a joy I won't forget.
Other inexcusable pain points: the build systems and package management is an absolute nightmare; the pre-processor feels like a sadistic joke; the syntax is horrible; there's so much cruft in the runtime you need years of experience to not machine-gun your foot off by using the most obvious tool at your disposal. But in a sense this just increases the joy of shipping a working executable with all your cleverness and blood and tears wrapped with a bow.
Kudos for one of the most relatable descriptions of C++ I've read.
I did a couple years writing C/C++ professionally, and I hope to not go back to that. Too many hours debugging other people's code, suffering vague integration issues, and just trying to get the build system spaghetti to run.
I just spent 3 days to statically link a third party dependency to my C++ project, in a way that works with linux/windows/macos. The package wasn't available on conan/vcpkg, it was just a github repo with a weird combination of Makefile/cmake file. While I'm far from a cmake/cpp expert, this is a non-issue with most modern languages: you just pip install, cargo add, npm install, go get etc. You can read front-to-cover books about every details of C++ semantics and it'll still be a PITA to work on real world projects
What was particularly difficult about it? Sometimes modifying someone else's code to build statically or on another platform is tricky. Linking is generally just include dir, lib path, and lib though.
- trying to compile against the prebuilt duckdb_static.so file got me a ton of undefined reference error, I asked about this on their discord and it seems to be a knwon issue, there are additional dependencies that are not part of the release so I had to build it myself
- the library uses CMake but it contains a Makefile and they recommend using that to build it
- It seems like on windows, if you create a dynamic library you have to add __declspec(dllexport) before each function. DuckDB has a #ifdef DUCKDB_STATIC_BUILD to toggle/disable it which isn't documented anywhere, you have to read the code
I'm sure someone will tell me that this is very standard and shouldn't take me 3 days to figure it out, but with basic knowledge of cmake and build systems that's a bit of a pain, and there's no other language where you have to do that
CMake, the defacto if not standard build system, when correctly setup, does all this for you.
Your argument is along the lines of "the maintainer of X rust package didn't setup cargo correctly and it's impossible to include in my project, rust is a horrible language". You can absolutely make an argument that the lack of a standard build system is a pain point, and well it's C++ you get to be a special snowflake and use a makefile instead of CMake but then the onus is on you to provide correct documentation.
In the case of duckdb they provide proper packages for pip, cargo, npm, go get, and the only one that seems like a PITA to use is the C/C++ dependencies, even though the package is written is C++, so I think this tells something about C++ dependency management
You seem to be agreeing with me then, weird take to do it while sounding like you disagree...
As I said, you can make the argument C++ build systems are no good. However the argument originally made is that C++ the language caused these issues when in reality the package maintainer hasn't put in the effort to properly document and fix known issues in their chosen build system for C++.
The fact that they in fact have built out proper packages for pip, cargo, npm and go tells me they have the necessary expertise on the team and made an active choice to not do the same for C++. Create an issue with the maintainer.
The argument originally made was "You can read front-to-cover books about every details of C++ semantics and it'll still be a PITA to work on real world projects"
Which is not disagreeing with you just because you are explaining/disputing the reason it was a PITA.
Seeing as it's Christmas Eve and I has nothing better to do...
Duckdb own documentation says the C++ API is internal and you should use the C API[1]
The makefile also has a 'bundle-library' option which seems to be exactly what you were looking for, it generates a statically linked library which is what the Golang package is using, probably others too but that's the one I checked first.
This is purely a documentation problem, the build system does what is needed it seems. Create an issue, or better yet, add the necessary documentation...
Created an account just to say: I am a 10-year JavaScript veteran who knows the ecosystem at least as well as any sane person could possibly be expected to, and this is 100% correct. When I tell people that JS build tooling is the second-worst headache to C/C++ build tooling, they laugh, but stick a toe outside the happy path of `npm create <some-framework-project> && npm start` and you will know what pain feels like.
I did get some similar vibes trying to work with Gradle and Maven for a project a while back, but I don't really live in that world so it's hard to say whether my experience was typical or just symptomatic of my inexperience.
And it'll continue to get worse because JS devs love coming up with their own abstractions over everything and piling complexity. You don't write JS, you write TS. You don't write HTML, you write JSX. You don't write CSS, you write Tailwind. And of course, React has its own compiler as of recently. Now you have to figure out in what order to run these parsers, bundle everything together including your hundreds of dependencies, minimize, obfuscate, tree shake, and whatever else fairy dust magic you want to sprinkle on top. Meanwhile, the default build tool of choice changes about once every 2 years, and now you also have a choice of runtime - node vs bun vs deno. Can't wait to see what the next revolutionary idea would be to contribute to this madness.
There are those of us out there that have a beautiful experience with JavaScript. Vanilla JS, web components, a lightweight rendering library like lit-html, class props as reactive state...
NodeJS + express + vanilla web components is the most graceful and productive stack I've ever used in 30+ years of development.
But to flip this around, let's say you want to use this third party in some other "modern" language how would this become better?. Clearly if someone hadn't done the work to package in vcpkg/conan you can expect it wouldn't exist in pip/cargo/npm either. So if you had a bad time in C++, you would have a dramatically worse time in other languages. Goes to show what everyone else is saying here, there are parts of historic C++ which are in very bad shape particularly the build story. However, if it's in vcpkg it is exactly as easy to install as the other ones.
> But to flip this around, let's say you want to use this third party in some other "modern" language how would this become better?. Clearly if someone hadn't done the work to package in vcpkg/conan you can expect it wouldn't exist in pip/cargo/npm either. So if you had a bad time in C++, you would have a dramatically worse time in other languages.
Err, no?
In case of Rust usually there is really no work that's necessary to package it and upload. You just type `cargo publish` and you're done (after filling out metadata in Cargo.toml). Even if someone wouldn't upload it to crates.io as a Rust crate you can just add a single line to your Cargo.toml:
I think the difference is that you can pip install a github url and it will work, it will even work if the upstream has done nothing to package it.
I think the issue could be broadly defined as how difficult it is how difficult it is to take someone else's code and use it as part of your project. For C/C++ the answer can be completely trivial to a massive pain in the ass.
Oh I dunno. With cmake it really is pretty straight forward. You can pull from VCS, a tarball, even use a git submodule.
If it's another Cmake project, that's the happy path. Easy peasy. If it's not you still have the ability to run ./configure && make or evoke whatever incantation is in the dependency's requirements. I'm not the hugest fan of cmake, but I've worked with it quite a bit and it's a much more pleasant story than other languages I've used a lot like python, JavaScript, OCaml (which I love), etc.
I do find rust and go dependency management a little simpler due to the prescribed nature of them, but once you get off the happy path, the flexibility of C land is tough to beat. It sort of matches the language stories themselves; you have more control and have to do a bit more yourself. It's all about trade offs.
I've done it before to integrate rust into polyglot build systems. There's a surprisingly long history of build systems trying and failing to implement rust builds without the "happy path" of simply wrapping cargo. As far as I know no one's ever succeeded.
The main difference being that Cargo is both for distributing and building locally AND it’s the blessed way of building all Rust projects. This means that as long as you have a copy of the codebase, it’s going to be using Cargo & you can add it with Cargo regardless of it being published formally to crates.io (the copy can even be remote since you can build from a git reference directly). Python packaging requires more work although there’s typically no build step so you can probably just __import__ the path and move on. JS is closer to Rust in that package.json is the standard way to manage components and you can import them even if they’re not published via the npm registry.
Pretending the situation isn’t very different for C++ is completely ignoring the standard committee’s complete abdication of standardizing a build system (which by the way if they’re not going to do, why are they bothering to standardize anything?)
This article seems kinda confused. It makes a lot of points, but I'm struggling to extract why that means C++ is more/less fun.
The simultaneous description of modern C++ as "extremely high quality" and yet pervasive legacy bs, horrible tooling, etc is confusing. They say unique_ptr is great yet they "really hate RAII". Many of the discussed topics are largely irrelevant or incorrect. Metaprogramming used to be everywhere but now isn't (what?? Take a look at concepts)? C++ has the best graphics libraries (surely not true)? Installing Python is harder than C++? These seems like odd obsessions of the author, with little relation to the argument.
I think the author just finds C++ fun for some complex combination of personal factors, which is fine. Perhaps partly due to a "hacker" type personality. Some of the most fun I've had with programming has been C++, due to its performance and technicality. At the same time, C++ is in practice crap for "real" software development where your own short-term enjoyment is not paramount (for a billion reasons we all know). C++ is a deeply conflicted language, scarred by decades of legacy and politics. Ignoring the reality of C++ is the biggest mistake of those who discuss it. My attitude towards C++ these days is of tiredness. I don't want to jump through the hoops C++ has thrown at me for the past many years. We don't need to beat an already long dead horse. It's ok to let C++ be what it is.
> I think the author just finds C++ fun for some complex combination of personal factors, which is fine. Perhaps partly due to a "hacker" type personality.
I think that sums up the point of the article just fine.
To me at least, it's not so much that it's confused as that it's him being excited about having fun and doing his best to share his joy. Which means that, sure, it's not a proper essay at all, it's not really trying to convince you to agree so much as saying "if you find this relatable without me trying to actively convince you, maybe you'd find it fun too."
I think given you've been mugged by reality in commercial work to the point of tiredness, your not finding it to make sense at all is completely understandable, though.
I'm currently having a blast writing javascript of all things, and I imagine if I wrote up a similar blog post explaining why it'd come across just as badly to somebody burned out on the last decade of commercial javascript too.
But I'm glad he's having fun, and I'm glad he decided to share the joy.
C++ really is a blast and I think the complaints people have about it really depend on context. Lots of C++ devs hate that language but imo misdirect their hate. They actually work on legacy products built by people who were never that great at writing software. This happened to me with Rust actually where I was thrust into an existing rust project at the company I work for. It's an "old" codebase with many different and conflicting conventions within it (different error handling types that don't compose well, bad class abstractions, lots of unsafe block when not needed, etc). The project was the most miserable project I've ever worked on and it's easy for me to blame Rust, but it's really just bad software development. There are warts in C++, but a lot of the pain people run into is just that they are working on crap code and it would be crap in any language.
In every other language (except C), basically every time you write v[x]=y you aren't inviting the possibility of arbitary memory corruption. The C++ 'std::optional' type makes calling '*v' undefined behaviour if 'v' is empty. The whole point of optional is to store things that might be empty, so why put undefined behaviour on the most common operation on an optional when it's empty (which is going to be common in practice, that's the point!)
The problem I have with C++ (and I've written a lot), is every programmer involved in your project has to be 100% perfect, 100% of the time.
I know you can run memory checkers, and STL debug modes, but unless you are running these in release as well (is anyone doing that?), then you are then counting on your testsuite hitting every weird thing a silly, or malicious, user might do.
Given how important software is nowadays, and how Rust seems close to the performance of C++, is it really worth using a language where so many of the basic fundamental features are incredibly hard to use safely? They keep making it worse, calling an empty std::function threw an assert, the new std::copyable_function has made this undefined behaviour instead!
The C++ 'std::optional' type makes calling 'v' undefined behaviour if 'v' is empty*
Are you talking about dereferencing a null pointer? That is going to case a low level exception in any language, it has nothing to do with std::optional.
every time you write v[x]=y you aren't inviting the possibility of arbitary memory corruption
That's how computers work. You either pay the penalty of turning every memory check into a conditional boundary check first or you avoid it all together and just iterate
through data structures, which is not difficult to do.
Also debug mode lets you turn on the bounds checking to debug and be fast in release mode.
The reality is that with a tiny amount of experience this is almost never a problem. Things like this are mostly relegated to writing your own data structures.
No, I'm talking about std::optional<T>. It's a type designed to store an object of type T, or nothing. You use '*v' to get the value out (if one is present).
It was originally discussed as a way of avoid null pointers, for the common case where they are representing the possibility of having a value. However, it has exactly the same UB problems as a null pointer, so isn't really any safer.
I'm going to be honest, saying that memory corruption in C++ is 'almost never a problem' just doesn't match with my experience, of working on many large codebases, games, and seeing remote security holes caused by buffer overflows and memory corruption occurring in every OS and large software program ever written.
Also, that 'penalty' really does seem to be tiny, as far as I can tell. Rust pays it (you can use unsafe to disable it, but most programs use unsafe very sparingly), and as far as I've seen benchmarks, the introduced overhead is tiny, a couple of percent at most.
You can pay the penalty and get safe container access, too. AFAIK, C++ standard containers provide both bounds checked (.at()) and unchecked ([]) element retrieval.
Now we are getting down to a philosophical issue (but I think an important one).
In Rust, they made the safe way of writing things easy (just write v[x]), and the unsafe one hard (wrap your code in 'unsafe'). C++ is the opposite, it's always more code, and less standard (I don't think I've seen a single C++ tutorial, or book, use .at() as standard rather than []), to do bounds checked.
So, you can write safe code in both, and unsafe code in both, but they clearly have a default they push you towards. While I think C++'s was fine 20 years ago, I feel nowadays languages should push developers to write safer code wherever possible, and while sometimes you need an escape hatch (I've used unsafe in a very hot inner loop in Rust a couple of times), safer defaults are better.
You have to do a very similar operation to has_value() .value() with Rust's optional though... How is opt.ok_or("Value not found.")?; so different from the C++ type?
It seems like people think dereferencing an unengaged optional results in a crash or exception, but ironically you are actually less likely to get a crash from an optional than you would from dereferencing an invalid pointer. This snippet of code, for example, is not going to cause a crash in most cases, it will simply return a garbage and unpredictable value:
auto v = std::optional<int>();
std::cout << *v << std::endl;
While both are undefined behavior, you are actually more likely to get a predictable crash from the below code than the above:
int* v = nullptr;
std::cout << *v << std::endl;
I leave it to the reader to reflect on the absurdity of this.
One is undefined behavior which may manifest as entirely unpredictable side-effects. The other has semantics that are well specified and predictable. Also while what you wrote is technically valid, it's not really idiomatic to write it the way you did in Rust, you usually write it as:
if let Some(value) = some_optional {
}
At which point value is the "dereferenced" value. Or you can take it by reference if you don't want to consume the value.
I've been wondering how much you'd pay if you just required that out of bounds accesses be a no-op with an option of calling an error handler. De-reference a null or out of bounds pointer you get zero. Write out of bounds, does nothing.
int a = v[-100]; // yolo a is set to 0
I really suspect that unlike the RISC mental model compiler writers think in terms of a superscalar processor would barely be slowed down. That is if the compiler doesn't delete the checks because it can prove they aren't needed.
It was originally discussed as a way of avoid null pointers,
I don't think this is true. I don't think it has anything to do with null pointers, it is a standard way to return a type that might not be constructed/valid.
I think you might be confusing the fact that you can convert std::optional to a bool and do if(opt){ opt.value(); }
You might also be confusing it for the technique of passing an address into a function that takes a pointer as an argument, where the function can check the pointer for being null before it puts a value into the pointer's address.
I'm going to be honest, saying that memory corruption in C++ is 'almost never a problem' just doesn't match with my experience,
In modern C++ it is easy to avoid putting yourself in situations where you are calculating arbitrary indices outside of data structures. Whether people do this is another story. It would (or should) crash your program in any other language anyway.
Also, that 'penalty' really does seem to be tiny
The penalty is not tiny unless it is elided all together, which would happen in the same iteration scenarios that in C++ wouldn't be doing raw indexes anyway.
>I don't think this is true. I don't think it has anything to do with null pointers
The paper that proposed std::optional literally uses examples involving null pointers as one of the use cases that std::optional is intended to replace:
Here is an updated paper which is intended to fix some flaws with std::optional and literally mentions additional use cases of nullptr that the original proposal did not address but the extended proposal does:
I am going to be somewhat blunt, but if you're not familiar with how some of the use cases for std::optional is as a replacement for using raw pointers, along with some of your comments about how C++ treats undefined behavior, as if it's just results in an exception, suggests you may not have a rigorous enough understanding of the language to speak so assertively about the topic.
I am going to be somewhat blunt, but if you're not familiar with how some of the use cases for std::optional is as a replacement for using raw pointers
I never said there wasn't a use case, I said it wasn't specifically about protecting you from them. If you put a null pointer in and reference the value directly, it doesn't save you.
If you don't understand the context of the thread start arguments over other people's simplified examples. I'm not going to write huge paragraphs to try to avoid someone's off topic criticisms, I'm just telling someone their problems can be avoided.
Yes you did and I literally quoted it, here it is, your words:
"I don't think this is true. I don't think it has anything to do with null pointers, it is a standard way to return a type that might not be constructed/valid."
This was in response to:
"It was originally discussed as a way of avoid null pointers,"
I have provided you with the actual papers that proposed std::optional<T> and they clearly specify that a use case is to eliminate the use of null pointers as sentinel values.
It's a template, you can put whatever you want in it including a pointer. All your example shows is that you can bind a pointer to a reference and it will wrap it but won't access the pointer automatically.
I'm not sure what your point here is other than to try to mince words and argue. It's a standard way to put two values together and what people were probably already doing with structs. You can put pointers into a vector too, but that doesn't mean it's all about pointers.
I'm pretty sure the issue that the parent commenter is referring to isn't about wrapping a pointer type in an optional, but wrapping a _non-pointer_ type in an optional, and then trying to access the value inside. std::optional literally provides a dereference operator operator[1] which contains the following documentation:
> The behavior is undefined if this does not contain a value.
The equivalent to this in Rust isn't `Option::unwrap`, which will (safely) panic if the value isn't present; the equivalent is `Option::unwrap_unchecked`, which can't be invoked without manually marking the code as unsafe. I've been writing Rust professionally for a bit over five years and personally for almost ten, and I can definitively say that I've never used that method a single time. I can't say with any amount of certainty whether I've accidentally used the deference operator on an optional type in C++ despite writing at least a couple orders of magnitude less C++ code because it's not something that's going to stick out; the deference operator gets used quite often and wouldn't necessarily be noticeable on a variable that wasn't declared nearby, and the compiler isn't going to complain because it's considered entirely valid to do that.
Is your argument that rust with panic if you are a bad programmer and C++ says its Ub of you are a bad programmer?
That's just fundamental difference of opinion, Rust isn't designed for efficiency, it's designed for safety first. C++ unofficial motto is, don't pay for what you don't use.
If I type *X why would I pay for a check if its empty, I literally should have checked the value isn't empty.
If you work in a code base with people who don't check, your codebase doesn't have static analysers, you do no code review and dereferencing an uncheck optional get's to production, do you think a .unwrap in rust wouldn't have made it to production?
Citation needed, because Graydon Hoare the original Rust creator (who has not been involved with Rust development for quite a long time) wrote about how the Rust that exists is not like the original one he was designing:
- "Tail calls [..] I got argued into not having them because the project in general got argued into the position of "compete to win with C++ on performance" and so I wound up writing a sad post rejecting them which is one of the saddest things ever written on the subject. It remains true with Rust's priorities today"
- "Performance: A lot of people in the Rust community think "zero cost abstraction" is a core promise of the language. I would never have pitched this and still, personally, don't think it's good. It's a C++ idea and one that I think unnecessarily constrains the design space. I think most abstractions come with costs and tradeoffs, and I would have traded lots and lots of small constant performancee costs for simpler or more robust versions of many abstractions. The resulting language would have been slower. It would have stayed in the "compiled PLs with decent memory access patterns" niche of the PL shootout, but probably be at best somewhere in the band of the results holding Ada and Pascal."
An unchecked dereference should also throw up flags during review in a C/C++ codebase. I didn't assume that nobody would make mistakes. My argument has always been that you use a language like C++ where needed. Most of your code should be in a GC language. Going in with that mentality, even if I wrote that code in Rust, I'm exporting a C API, which means I may as well have written the code in C++ and spend some more time in code review.
EDIT: an unwrap that crashes in a panic is a dos condition. In severity this might be worse or better depending where it happens.
Both are programmer error, both should be caught in review, both aren't checked by the compiler.
It would (or should) crash your program in any other language anyway.
To me there is a massive difference between "there is a way a malicious user can trigger an assert, which will crash your server / webbrowser", and "there is a way a malicious user can use memory corruption to take over your server / webbrowser".
And bounds-checking penalties are small. I can tell they are small, because Rust doesn't have any kind of clever 'global way' of getting rid of them, and I've only noticed the cost showing up in profiles twice. In those cases I did very carefully verify my code, then turn off the bounds checking -- but that's fine, I'm not saying we should never bounds check, just we should do it by default. Do you have any evidence the costs are expensive?
If you profile looping through an array of pixels in linear memory order vs bounds checking every access it should be more expensive due to the branching.
As someone else mentioned you can boundary check access if you want to in C++ anyway and it's built in to a vector.
My point here is that it isn't really a big problem with C++, you can get what you want.
Iterating over an array in Rust does not involve bounds checking every access since Rust guarantees immutability to the container over the course of the iteration. Hence all that's needed is to access the size of the array at the start of the iteration and then it is safe to iterate over the entire array.
Note that this is not something C++ is able to do, and C++ has notoriously subtle iterator invalidation rules as a consequence.
and you haven't given evidence of bounds checks being expensive. They didn't say it was free, they said "bounds-checking penalties are small". You've described a situation where bounds checking happens and simply declared that it "should be" expensive.
I really can't resonate with this. The type system is obnoxious (and you don't even get proper memory safety out of it), the "most vexing parse" truly is vexatious, there's all the weirdness inherited from C like pointer decay (and then building new solutions like std::array on top while being unable to get rid of old ways of doing things), and of course the continued reliance on a preprocessor to simulate an actual module import system (and the corresponding implications for how code for a class is organized, having to hack around with the pimpl idiom etc....)
Essentially, the warts are too large and numerous for me to find any inner beauty in it any more.
> and of course the continued reliance on a preprocessor to simulate an actual module import system
C++ modules are supposedly meant to save the day. Although only recently have the big three compilers (GCC, Clang, MSVC) reached some form of parity when compiling modules.
I mean, this is all anecdotal, but I've only ran into the most vexing parse a few times, I rarely had to use the pimpl idiom, and the header file stuff... okay that isn't great. I've been a c++ dev for 10 years and I actually worked on an extremely old codebase and was involved in modernizing it is to C++11. Maybe I'm too C++ brained, but all those things just aren't that bad? There is no wartless language and you just deal with the warts of the language you're in and it's your responsibility to learn the details of the language.
The header stuff is pretty bad. The rest are strange choices of stuff to complain about. The vexing parse was never a big deal and these days with bracket initialization (which only rarely you can't use) even less. The pimpl idiom: not sure why it's really a problem. Plus you have many choices for type erasure in c++.
>The pimpl idiom: not sure why it's really a problem.
Because it's even more boilerplate and adds indirection in a place where you might have been painstakingly trying to avoid it (where the first indirection costs you all your cache coherency). C++ lets you go out of your way to avoid objects having to carry any overhead for virtual dispatch if you aren't going to use it; but then you might have to choose between rerouting everything through a smart pointer anyway or suffering inordinately long compile times. Nothing to do with type erasure (if I'm thinking clearly, anyway).
True - but it’s much easier to make an incomprehensible mess in more complex languages. Whereas blub language projects are pretty easy to decipher regardless of the state of the codebase.
Eh, I'm reminded of the old "programming languages as weapons" comic, with the one that I still remember is JavaScript was a sword without a hilt, with the blade being the "good part" and in place of the hilt, another blade labelled the "bad part".
You can blame devs instead of tools all day long, but you can't deny that there are things about the tools that hold the developers back.
I periodically work on c++ instead of C and each time it follows a similar pattern where I learn about some new c++ trick, think it will make things better, write my code, hit a compiler error, then spend the rest of the day learning why I can't do what I want. Granted, I usually am stuck at c++14, and some of the issues are fixed in future versions but still...
I really want to love C++. It gives me a more powerful C that theoretically should improve my output but in the end it carries so much cognitive baggage that I usually end up writing "C with classes" and move on.
> then spend the rest of the day learning why I can't do what I want
There's a point when learning's fun, I think the OP is still there.
I wrote a bunch of realtime C++ in 2003, hated it. But last year, I wrote most of my code in C++ and liked it finally.
Lambda and auto was the tipping point where I stopped hating it. Some templates thrown in, but mostly to avoid writing a lot of branches for variants of the same function.
With lambdas I could now write the way I was initially taught programming, mostly by Lispy teachers channeling SICP.
Didn't hate the allocations either with unique_ptr.
I see a lot of bad code from people who have just learned about some feature and want to use it. Don't do that. Think of how to write your code, and then, if a feature solves a problem, use it. Problem->Solution, not the other way around.
E.g. C++ templates are generally pretty awful, but sometimes, compile-time duck typing, or automatically adding padding based on sizeof(), etc, is very useful.
Sure, but the effort/reward ratio bears some consideration. I've put a lot more effort into learning C++ than any other programming language but I'd still say it's just the language I'm maybe 4th most proficient in.
> C++, like most technology, works better if you put in the work to learn about it.
There's also the impact of software entropy: if someone has little to no experience developing software and has to grow it by adding features and fixing bugs, over time their contribution to the project invariably results in degrading it beyond the point they can possibly salvage it.
Programming languages are a means to an end. I'd rather have my cognition going towards solving the actual problem and not worrying about implementation details.
Python does work better if you put in that kind of work. Otherwise you'll get bit by the way mutable default arguments work[0] (or never learn to use it to your advantage), or by late binding of closures[1] (and maybe you'll pick up the awful habit of exploiting the confusing early binding of default arguments to make the `lambda`s that you constructed in a loop work properly[2]). Or you won't get the big picture of the descriptor protocol[3] and how method lookup[4] is related to the implementation of properties[5]. Or you won't get the fancy metaclass[6] thing that your favourite framework is expecting you to treat as an opaque abstraction[7], or how and when you might use a class decorator[8] instead of a metaclass to do that kind of metaprogramming.
This describes my C++ experience precisely. Initially I was a C# programmer learning C for embedded projects and it went exactly like this.
C++ has a lot of really neat features that sure look powerful if your application aligns precisely. It seems like every time I try a new feature, what I want to do is always an edge case that doesn't work in my situation. I try for a few hours/days before I give up and just write it in C.
My biggest disappointment with C++ is that the standard libraries are completely unsuitable for use in embedded systems where you have to control when, where and how many memory allocations occur. This is particularly important when the system level design choice is to perform all allocations at system startup, which is a common design pattern for high performance systems with real time characteristics. A high speed messaging system I worked on ran into this all the time. We couldn't use the standard implementations of things like heaps, hashes or queues because they don't have a way of making memory allocations occur at startup. It was quite common to have to re-implement those data structures when adding a new feature, as the stl or boost implementations were not suitable for this design pattern.
> They’ll still bullshit you and send you on wild goose chases
And confidently at that. It can't seem to find the backbone to say no to me either.
If I say something like "wait, X doesn't seem to make sense, isn't it actually Y and Z?" it will agree and reformulate the answer as if Y and Z were correct just to placate me. I usually use the LLM to learn new things, I don't don't actually know if Y and Z apply.
Agree. C++ is here to stay though. Too much code written in that language. It's certainly a great skill to have, and there are super lucrative jobs too.
For me, I feel the language just go in the way and is way too complex. Sure, I know the discourse: "modern C++" is great, you don't need to learn about older. versions of C++, things are getting simpler with each new version.
The problem is that the codebase you get to work on aren't modern C++. They use every possible feature ever released and it's just hard and full of pitfalls.
I suppose people who have only work on C++ in their projects for years can develop some level of expertise and are immune to the complexity. But really, not a great language...
One of the things that has made C++ more tolerable over time is that it became easy to seamlessly replace most of it with an implementation that works the way you think it should and with most behaviors being fully defined -- the core data types, standard library, etc. Particularly with C++20 and later, alternative foundations feel quite native and expressive.
Most new C++ projects I see lean into this quite heavily. The ease with which you can build a systems-level DSL for your application is a strength of C++.
I take a few features from C++ in my C++/SDL2 Ultima spinoff project -- never completed sadly. Class, std::function, std::unordered_map, std::string, std::unique_ptr<type> are the only ones that I could recall.
I can't imagine reading other people's code though, unless it's in a similar style. I did find that the most difficult part is to get past the programming patterns (such as Visitor pattern) as I don't write large programs professionally.
I wish C++ stopped at C++/11. The committee seems to want to include everything into it at the moment. Maybe C++ is sort of ULTRA programming language that supports every niche programming style.
Ah that was my pet project to create an engine that runs a game similar to Ultima 1-3. I even used the Ultima 4 sprite sheet. Never completed it though.
But it's a fun mess, and I like writing in it :) Sometimes, that's important.
My relationship with, say, Rust is much colder: I respect Rust, I use Rust where I think it makes sense (especially in e.g professional contexts where I often can't defend the use of C++), and I think what it's doing is important. But I find it tedious to write, despite having a fair amount of Rust projects under my belt at this point. It's better in key ways, but not as enjoyable, for me.
Rust is fun for me but I keep it high level with Rc and built-in data structures. It's fun thinking about variables as little boxes of fixed size that values get moved into or out of. It's so different than almost any other language where stuff lies often god knows where and is referenced ad nauseam. Although that can be fun too if the language treats mutability as exception rather than a rule.
> I'm glad someone is having fun in C++. Personally, after >20 years, I have to be done with C++. It’s just a mess.
I've spent a couple of decades working with C++, and the truth of the matter is that, as much as it pains the bandwagony types, C++ became great to work with once C++11 was rolled out. The problem is that teams need to port their projects to >C++11 and that takes an awful lot of work (replace raw pointers with smart ones, rearchitect projects to shed away C-style constructs, etc) which some product managers are not eager to take.
I firmly believe that the bulk of this irrational hatred towards C++ comes from naive developers who try to rationalize away their lack of experience by blaming everything on the tool. On top of this, cargo-cult mentality also plays a role.
What makes this quite obvious is the fact that the solution presented to all problems always comes in the form of major rewrites, even with experimental and unproven features and technologies. Writing everything again is always the solution to everything. Everyone before them knows nothing whereas these messiah, cursed with being the only ones who see the one true answer, always have a quick and easy answer to everything which always boils down to rewriting everything from scratch with the flavor of the month. Always.
> C++ became great to work with once C++11 was rolled out.
Have Yossi Kreinin's objections (https://yosefk.com/c++fqa/defective.html) been addressed yet? In particular, can I reuse source code from another file without a text preprocessor yet? Can I change a class' private members without recompilation, or am I still stuck with indirecting that behind a "pImpl" pointer in the class declaration in the header file? (Being able to use a smart pointer for that is not addressing the problem.) Are compiler error messages resulting from type mismatches reasonably short (without third-party tools like STLFilt) yet (never mind whether they're informative or pleasant to decipher)?
I know that "some parts of the FQA are not up to date with C++11 [and onward]", but I haven't heard of these problems being fixed.
A cursory read of that list is enough to see it's a list of complaints fueled by a mix of ignorance and disingenuity.
For example, the first entry complaining about "no compile time encapsulation" is somehow completely ignorant and oblivious to very basic things such as the pimpl idiom. I mean, this thing is notorious in the way it allows Qt to remain binary compatible across even major version bumps. And yet, "This makes C++ interfaces very unstable"?
The list reads like pure nonsense, to be quite honest. At some point the author gripes over the lack of garbage collection. Which language lawyers know very well that until somewhat recently C++ standards actually had provisions to explicitly support it, but whose support was removed because no one bothered with it.
Yossi is aware of the pImpl idiom and refers to it explicitly in section 16.21 of the FQA. It adds the overhead of indirection; in particular, it means that even when you don't use polymorphism and were able to avoid the cost of a vtable, you still don't get to have an array of instance data contiguously in memory. And it's still something you have to do manually; you don't get it by default. It seems clear to me that this simply doesn't meet Yossi's standard for "compile time encapsulation".
>At some point the author gripes over the lack of garbage collection. Which language lawyers know very well that until somewhat recently C++ standards actually had provisions to explicitly support it, but whose support was removed because no one bothered with it.
Other people not caring about garbage collection doesn't mean it's a missing feature. It's clear why operator overloading in particular would benefit from the ability to make temporaries without worrying about the memory they allocate. (Of course, this was written in an era with a much poorer ecosystem of "smart pointers".)
>Is this your best reference?
It's not as good of a reference as I remember it being, I suppose. It has been a long time. But what I've seen of C++ in the interim, bit by bit, has generally made me less inclined to pick it up again, not more. The complexity just doesn't seem justified.
> C++ became great to work with once C++11 was rolled out. The problem is that teams need to port their projects to >C++11
The problem is the C++ that's not great to work with is still there, and there's nothing preventing the rest of the world from using it; there are always going to be naive developers with a lack of experience who don't know how to use the tool. For this reason, all the code that's possible to write in C++ will be written, that includes the unsafe code.
It's not enough to have a safe, nice, modern, subset of C++ that everyone "should" use. If developers have the option to use the warty, sharp, foot-gun infested version of C++ they will, and they will gift that code to the rest of us in the form of readily exploitable software.
> companies should investigate memory safe programming languages. Most modern programming languages other than C/C++ are already memory safe. Memory safe programming languages manage the computer’s memory so the programmer cannot introduce memory safety vulnerabilities. Compared to other available mitigations that require constant upkeep – either in the form of developing new defenses, sifting through vulnerability scans, or human labor – no work has to be done once code is written in a memory safe programming language to keep it memory safe.
> The problem is the C++ that's not great to work with is still there, and there's nothing preventing the rest of the world from using it;
That's precisely why all this criticism is actually thinly veiled naive inexperient developers blaming the tools. Selling full rewrites as solutions to the problems they created is a telltale sign. As they are lacking the experience and know-how to fix the mess, they succumb to the junior dev disease of believing deleting everything and starting from scratch is a solution to all of life's problems and inconveniences.
That's not the problem. It's naive inexperienced developers using the tools. Most developers have to maintain code they didn't write themselves. One can learn all the C++ best practices in the world, but it won't protect you from other people. That's why languages with strong restrictions and constraints that force safety and correctness are needed. With such languages, naive inexperienced developers won't be able get anything to compile. We won't have to deal with their mistakes as they'll never be able to ship them. Any experienced developer would surely want this.
A rewrite is not pointless if you are rewriting into a language with additional guarantees. You are checking for and proving the absence of certain classes of software flaws by doing so.
Is Rust not meant to offer the same kind of control and "low-level-ness" as C? Like, can't you cast integers to pointer explicitly if you break the `unsafe` seal (and thereby e.g. do memory-mapped IO to control hardware)?
> Those C89 hardliners have a different point of view.
C89 was only a thing because Microsoft somehow decided to drag it's feet and prevented msvc from supporting anything beyond c89 for a long, long time. Only in 2020 did they manage to finally do something about it.
Not in the embedded space. When you've worked on embedded systems long enough, you learn that you have to accept the compiler that the vendor provided you with, and you adapt your codebase to the limitations of that compiler. This means working around code generation bugs, adding #ifdefs to define typedefs for things like int16_t if they don't exist.
That said, things are a lot better than they were 15 years ago, and the mainstream ARM compilers used today are leagues better than the barely functional cobbled together ports from the early '00s. ARM64 is a tier 1 platform, and there are enough users of the platform that the extended QA team that embedded developers were unintentionally part of in the past is no longer really the case (at least when it comes to the compiler).
However, there are still truly obscure architectures (mostly 8 bit and 16 bit) that continue to have oddball compilers. And you accept and deal with that insanity because it means you're only paying $0.01 for that microcontroller to blink an LED when some kind of an event occurs.
This is a long rant that covers a lot of ground, a lot of which will inevitably be ignored because the letters "C++" trigger people, myself included sometimes. (Skip ahead to "It's Not All Puppies and Butterflies" for some of the complaints.) The author is really impressed by C++11, as am I, after purposely ignoring C++ for the better part of twenty years.
I appreciate the shout outs to some packages and libraries to play with, although I still often find it a pain to incorporate other libraries into my projects. (Single-file headers, anyone?) I'm intrigued by FTXUI.
And boy, howdy, he's right: cppreference.com is amazing. Python's documentation is pretty good, but I've never seen anything as good as cppreference.com.
cppreference.com is very, very good at being reference, as in the name.
Python's documentation is scattershot and incomplete in many places, and lacks a consistent copy-editing style - but it offers good coverage of all kinds of documentation (per the Diataxis framework), not just reference. The people writing that documentation explicitly take that framework into consideration and use it to look for ways to improve. (But it's still a volunteer effort that works basically the same way the code development does, following open-source principles, so.)
The article filled me on a lot of things I didn't know about C++ because I learnt it at school and college, but soon moved to Python/JavaScript for day job. I have been itching to "get closer to the system" for a while now, and learning Rust on the side hasn't been easy. This article gave me hope that, I might be able to do that if I refreshed C++. Hello CMake… or I should probably say, Meson.
If you're starting a new project with a talented team that knows how to use modern C++ well, I agree that C++ is great! It's a pleasant and powerful language, delivers great performance and (while complicated) is straightforward to debug and optimize.
I had the privilege of working on a codebase that was about 5 years old, written to C++11 standards or later, and I thoroughly enjoyed it, for many of the reasons expressed in the article.
If you are working on an older codebase that has evolved over 20-30 years, or one that has not been maintained by talented people, you will have a very different experience.
I started my life as a dev with PHP, learning it on the job to set up a custom Wordpress theme with plugins for special datatypes (eg STCs).
When I was 21, they offered to pay for my tuition so I went back for a CoE degree (later switching to CS). The moment I touched C++ - on my very first day in their “intro to programming” class - I fell in love. The type system, debugging, and ecosystem was fantastic compared to PHP 7 that I was using at the time. With my manager's permission, I went back to some of our existing intranet apps and built a C++-based API for them that our PHP would call over a web-socket, so that I could leverage the type system (and performance). Those systems are still in daily use, company wide. Maintenance hasn't been a problem. And that’s despite running in a really weird environment (IBM Pase for i).
Now, after using Rust and Axum, I'd vastly prefer the Rust ecosystem for creating that type of API. But C++ deserves credit. It really is a great language, especially if you’re coming from web languages like (non-TS) JavaScript or PHP.
> If you are working on an older codebase that has evolved over 20-30 years, or one that has not been maintained by talented people, you will have a very different experience.
I'm trying to think of any other language that could fit that description and not also be a terrible experience. In fact, I'd prefer C++ to many other possibilities I can think of (C, Java, Fortran). The exception would be C#, but I'm not sure if it's old enough to fit.
Fair point, although I think it's true that C++ gives you more rope to hang yourself with, and if I had to choose, I'd rather maintain a crusty old codebase written in Java than one written in C++.
You can and always have been able to .destroy() threads in java, but doing so breaks the memory model in ways that make it clear that you should never do it. My guess is that if you're kill heavy in c++, then you've also run into bugs waiting on locks of threads you've now just murdered. That's why there's a ton of great abstractions for how to "kill work running somewhere else" that doesn't leave your application in a potentially unstable state.
Java 9+ sure did kill off a lot of things that weren't particularly ideal. Most normal devs who don't write libraries probably can't even name a single feature removed (corba, script interpreters, some jmx cruft, etc). Most of what was culled still exists in libraries that could easily be added back with project imports. Maybe Java applets are super dead now? I don't believe any modern browsers still support native plugins, but maybe there are some niche individuals bemoaning the loss of java web start.
As for the "can still deploy code" comment, the same applies to java. Look around, and sadly you see very old releases of java still in use today. My guess is that if I fired up a java 1.0 compiler and JVM today, it would run on my PC (poorly).
Before criticising something, please consider being well enough informed to warrant the comment.
The underlying issue is that for the matches the interface relies a lot on heap allocations for the individual matches, leading to a lot of allocations of small regions to copy from the original input. Many other libraries provide a lot more control there.
In benchmarks std::regexp often is a lot slower compared to other implementations, independent from the standard library implementation of choice.
The big upside compared to all others is that it's always available. But if there is a choice alternatives are often better.
I learned C++ and enjoyed it but never went too deep. I always enjoyed C more. There's also so many S tier codebases to read to learn C better like the original Dune game engine or Unix utilities.
Having dabbled a bit with Rust recently I can't see any strong reasons to use C++. The combination of strong functional programing inspired type system with performance seems unbeatable. Almost a perfect language.
I'm sure there must be some legacy reasons to use C++ though. Maybe Game Engines, embedded programing, some kind of other legacy tie in?
Huge amount of legacy across many dimensions, not just apps written in it
[1], like number of users, published and available knowledge / resources (books, courses, blogs, articles, videos, software libraries, etc.), high compatibility with another huge language (C), etc.
This is just software industry general knowledge, for those who have been there for more than a few years in the field. I am not even a proper beginner in it, because I have never used it much, although I had bought, read and to some extent, understood some classic C++ books, including by the language creator (Bjarne Stroustrup [2]), Scott Meyers [3], and a few others, earlier. I did have a lot of experience using C for many years in production, though, including on a successful commercial product.
On most metrics I've seen Rust is comparable on general speed.
Maybe if you're at the level where you've essentially writing portable assembly and are okay with lack of safety. You need to know exactly what is happening within the CPU, maybe on custom hardware.
I bet some defense applications would be in this category too, although for my own sense of self preservation I would prefer the Rust type system.
Rust would probably be a good fit for HFT, but as the field is so dominated by C++ is hard for another language to make inroads. Java managed to some extent.
It is an abstraction, but the safety requirements are neither enforced nor abstracted away.
C's type system can't communicate how long pointers are valid for, when and where memory gets freed, when the data may be uninitialized, what are the thread-safety requirements, etc. The programmer needs to know these things, and manually ensure the correct usage.
Using C++ after Rust made me appreciate the latter a lot more. You quickly learn all the footguns that the compiler stops you from doing and is generally a good learning experience.
This is what I feel as well. I liken it to using an aimbot in Quake. Turn off the aimbot and you still win because the aimbot trained you how to get headshots. There are many times the Rust compiler told me I couldn't do something I had insisted would be fine, only to ponder and realize in fact what I was doing would cause subtle bugs in an edge case. Rust catches it at compile time, C++ allows you to write the code and sends you a segfault when the subtle edge case occurs in production.
Segfaults in production are the good case. They're when the system recognizes you've made a mistake and stops it from propagating. The bad cases are when the program keeps running but silently does the wrong things.
Yes! This can be a real problem when your data structures are allocated from a memory pool. Since the whole memory region is owned by the program, out-of-bounds writes will either do nothing or silently corrupt your program state.
Strings in Rust aren't that great, because there are some details you must keep track. But when I was first learning it, I eventually had some desire to rewrite stuff in C++, and always stopped at the first thought of dealing with strings.
I worked with c++ for many years and it has lots of warts. Slow compilation, lack of decent build system (no cmake isn't that great), mistakes made in the standard library (iostreams), weak cross platform standard libraries were always a headache.
But I always loved the power that <algorithm> and the like provides, although attaching allocators (and other template things) kind of sucks.
When I looked at and test drove rust all I saw was heaps of complexity stacked up yet again. Probably didn't help that I smacked headlong into the Arc Mutex and lifetimes mess. I'm really more inclined to go for something like 'zig' which does so much with simple syntax (and awesome comptime) and still gets excellent performance.
That's pretty damn impressive! How did you manage to avoid codebases with CMake? Or are you just saying you personally chose not to use it for your own?
A well written blog post that makes many valid points but comes with a wrong assumption: that making a language creativity-friendly is an absolutely desirable trait.
It's a desirable trait for my personal projects, where I may use Haskell, Ruby, Ocaml, Racket or something more exotic.
At work, I'd rather use languages that are boring, with well defined and uncreative patterns and practices. Professionally I expect to not be surprised often and I want the smallest group cognitive load possible.
Languages that breed too much creativity tend to have a rather short list of killer software (that kind of software that makes it worth learn a specific programming language).
I wonder if Zed started using, or went back to, Perl after getting so worked up about the 2 -> 3 transition. (From what I can tell, he's still complaining about it, and still wrong in many of those complaints, and makes unsubstantiated claims in others.)
For me, C is fun until I hit a certain level of abstraction complexity involving fake homespun vtables and it starts getting harder than it should be to chase down bugs.
Everytime I need to write C, I wish I could use C++ instead. The things I miss most are RAII, templates, lambdas, const-correctness, and most importantly, a standard library with container types and algorithms.
I agree, I switched from C++ to C and I found it relaxing to be able to just forgot about a million language features and their complicated interactions.
I also find you have some experience, know how to build good abstractions and have a set of good data structures, there is no issue with address complex problems in C.
enum { nb = 1024 };
struct { int k, v; } hash[nb];
// 0 is an invalid key
void incr(int k)
{
int i = k % nb, j = i;
do {
int k2 = hash[i].k;
if (k2 && k2 != k) {
i = (i+1) % nb;
continue;
}
hash[i].k = k;
hash[i].v++;
return;
} while (i != j);
abort();
}
Apologies for the telegraphic variable names and weird control flow. I wrote this on my cellphone. Lacking these 15 lines are what keep you from writing C?
There's a nice tutorial on hash tables in K&R, and I can also recommend learning about Chris Wellons's "MSI" hash table design, described in C at https://nullprogram.com/blog/2022/08/08/. He shows a more full featured hash table in about 30 lines of code, depending on what functionality you need. It's eminently practical, and usually a lot faster than generic algorithms.
That's not an exceptionally simple hash table either. One night I hurriedly wrote a rather long-winded implementation also on my cellphone (strings, with separate chaining and a djb-style hash function) and it also came to about 30 lines of code: http://canonical.org/~kragen/sw/dev3/justhash.c
I'm gonna be weird and not engage in language war (I love c++)
but, dear author:
> +95% of the compiler errors
this. this is unforgivable
+X means "additional X". As in "I have Y and I add +X to it". When you are invited to an event, your invite states you can take your +1 with you (one more person that will come within the same invitation)
if you want to say "more than", you use X+ !! as in "95%+". Because you have some X amount, and you add some more to get X+...
Come write products on top of Unreal Engine, you will have the opportunity to dive into 20+ MLoC of real time C++ goodness. Make sure it's a multiplayer experience for bonus points, eventual consistency makes everything extra exciting.
It gives you an appreciation of just how unlikely we're to ever move away from the stuff, short of an LLM innovation that can digest codebases of that size and do an automated port, which I suppose is not outside of the realm of reality these days.
I think we can start with "C++ is not popular enough to attract the weirdos".
Presumes C++ is not popular and also popularity attracts weirdos. If anything, weirdos are attracted to languages that are not popular at all. I remember once I was on a small language project and this guy on the mailing list wouldn't stop going on about how our language had to support vorpal math.
> Well, actually the Rust weirdos will bother you but they're usually too busy making the borrow checker happy to actually get anything done so you can ignore them.
While it is a great language, ir would profit from less "lets code C with C++ compiler" attitude.
Basically it is like renaming those JavaScript files from .js to .ts, and keep coding as if nothing else is available as productivity and safety improvements.
I feel like an abused spouse after C++. I now avoid:
- Inheritance
- Reference counting
- Threading
- Templates
- Classes if possible
- Hidden memory allocation
- Anything that looks clever
Anytime I use them I get flashbacks to some mangled mess of templated threaded classes with some memory leak that shows up after 3 days.
I remember writing C++ and trying to figure out how the design would work between these classes, I would end up with something complicated and not entirely correct. Eventually, I thought, what if I did this in C? What would it look like, 90% it turns out with 90% less design and code (and bugs).
You don't have to use any of that and you still get lots of nice things like range based for loops, STL containers, algorithms, namespaces, and constexpr.
Having read the article, nothing really stands out to me as "C with C++ compiler".
It talks about ranges, shared/unique pointers, lambdas... Essentially a lot of things C is lacking. I don't know where exacly the overlap you're insinuating comes from.
Pronoun confusion. The second pronoun is ambiguous.
Since we are all “hackers” here, I’ll be pedantic…
“While it is a great language…”
The “it” pronoun clearly refers to the C++ language, as I’m sure you intended.
“…ir would profit from less ‘lets code C with C++ compiler ’ attitude.”
The “ir” — presumably a typo for “it” — can refer to the article or C++. Given that this thread is about an article, the second “it” referring to the article is a natural assumption.
OK, so I admit I also washed my hands of C++ sometime around 2009 and I am being forced back into for <reasons>, and I had no idea what these auto and lambda keywords were.
Can anyone point me to a learning reference that will let me jump the meta programming apocalypse and just get to the good stuff?
I've had a similar situation with Kotlin. I've always been a java developer, and I enjoy using it, but even with the newer features it's just...slow.
When I had to script things I chose JavaScript (native JavaScript) since it's way faster to iterate, but I've always missed the static typing (I also know python, but I honestly prefer JavaScript)
Until I learned Kotlin. It's been a blast to use, incredible common libraries, streams everywhere, nulls that you can use without issues...I just love it (so much in fact that I'm in the process of switching project from java to Kotlin).
When I need to do scripts, like for the advent-of-code, I choose Kotlin.
I also really enjoyed the jump into Kotlin as well! That said, I'm also very happy with recent java versions trying very hard to close the gap. It does feel like at least half of Java's recent major lifts have been to emulate things done well in other languages (a great attribute for a self-aware language developer). I definitely never appreciated a lot of their newer feature choises like records until I saw them working really well in Kotlin data classes.
For me, it's basically C++ for low-level, Python for high-level, Java for insanely large corporate codebases, JavaScript (oh boy...) for the Web. Tons of better languages are out there, but they lack the same level of support, tooling and libraries' availability.
I learned c++ after c++20 and after several attempts to enjoy rust, c#, go, and C, I always come back to c++ as the most enjoyable language to develop.
> Need package management? Check out Conan, Meson's WrapDB, and vcpkg.
No, just no. All of them are complete, utter crap that doesn’t hold a candle to languages that were designed with packages in mind. We’re using Conan at work, I’ve been using vcpkg at home and I loathe both.
> I mean, do you really think Python's package management is top notch? You do? Why are there like 10 package managers then?
Worst Python package manager runs circles around whatever C++ offers.
I don't think we need to pretend Python package management is good to contrast C++, they're both awful for different reasons.
At least there's maybe a way out for Python (uv / the various PIPs); C++ doesn't appear to have any kind of plan whatsoever, outside of gesturing in the direction of modules.
> I remember insane discussions with people who thought adding two numbers with a template was "faster" than adding two numbers directly, even though the assembly language output was exactly the same.
I know almost nothing about C++ and have never programmed in it... but I thought that the point of that kind of 'template metaprogramming' was that the code got executed at compile time instead of runtime?
i.e. instead of generating identical assembly output the goal would have been to output a constant value
As per usual with c++ it depends. In some situations things are guaranteed to be compile time evaluated. E.g. even in early templates this would need to work:
Func<1+2>();
Something like this there's no guarantees around:
template <A, B>
int Func() {
return A + B;
}
For almost all compilers it should constant fold this into a constant but in theory it could end up with an add instruction. Basically we can't second guess the author here because it depends on the specifics.
For the love of God when will c++ compilers finally be able to output template errors that aren't completely expanded and are written in terms of the user's typedefs? Most of the time I spend parsing template errors with boost is just to figure out what the hell is being complained about.
Weren’t concepts supposed to fix this? Apparently they made it into the 2020 standard. I haven’t touched the language in many years - did they not help?
Concepts are in C++20. I don't know the specifics but it's my understanding that the version we got is stripped down in comparison to the original proposal.
I have found LLMs are a great tool for metaprogramming. I think the template error problem has been wanting for a sufficiently advanced compiler, and that's what I see LLMs as being. ChatGPT has been a great help in debugging programs I've written in C++ templates, both in generating the template code and trying to decipher errors generated, leading to suggestions for the template code rather than the expanded syntax.
Yeah, totally. I find LLMs are very useful for doing stuff with the preprocessor, too. ChatGPT taught me how to use boost preprocessor (BOOST_PP_FOR_EACH_PRODUCT).
Still though, I want to see MyMapType::value_type in compiler errors rather than... Well, you know. It's going to contain the type of the key, the type of the value, the type of the allocator, just when all you want to really know is that it's a pair<key, value>, which I think most people know of as My map type::value_type.
I've had some good times in C++. But for everything that's been thrown into it, I can't believe we're still dealing with header files. That was one of the greatest things about moving to Swift: no more of that BS.
But with SwiftUI, Swift has also become "unfun." SwiftUI and Apple's half-assed, broken observation and "reactive" paradigm have made programming a joyless slog.
I did C and C++, then moved on to Objective-C and Swift. I recently switched back to C++, after getting tired of Apple’s shit treatment of developers. I also have no interest in learning SwiftUI.
Having to define header files in C++ is pretty annoying after doing Swift for many years.
> I've had some good times in C++. But for everything that's been thrown into it, I can't believe we're still dealing with header files.
There is nothing wrong with header files. In fact, there is no such thing as a header file specified in C++. There are only forward declarations and definitions, and how those can be managed by developers.
Meaning, any talk about header files is a discussion on software engineering practices, and how developers create or avoid their own problems.
C++20 has modules, which replace header files completely (unless you use old libraries which aren't available as modules yet). Compiler support is there, but unfortunately IDEs are lagging. If you use modules with Visual Studio, say goodbye to IntelliSense. Maybe they'll iron out the bugs in a couple years...
They needed the most powerful, most flexible module system ever, so it might take decades to really become useable. Adoption has been painfully slow so far, it's insane complexity really doesn't help.
You can use modules to structure your own codebase. No more need to write headers and think about how to structure your code in terms of compilation units. But yeah, your link shows that practically none of the popular libraries (except STL) can be imported as modules today.
I have the luxury of only writing software for myself, which is almost always Python, but after reading this ill go back and have another look at C++. Did one big project in C++ after uni but then never touched it again.
It takes some getting used to. I wrote it for about 10 years then didn't touch it for 10 years and now I write it full time again. Took a few months to get up speed again and stop shooting myself in the foot.
> I firmly believe that for creativity to blossom you need to be able to get your idea out without fear of criticism and shame.
This section is, it seems to me, the linchpin of the "fun" argument. But you've been able to do that with all the other languages too, all this time. The much-loathed and feared Rust Evangelism Strikeforce doesn't actually come to your house and make you use a bunch of generic-heavy code from crates.io. The React people can't stop you from using vanilla Js. The worst they can really do is send you mean tweets, but Shaw thinks this is a lethal threat to his creativity, enough to switch language ecosystems over. For an article written in a superficially rebellious, lone-wolf tone, that's kinda sad.
I'm baffled by this. Deterministic destruction (necessary and basically sufficient for RAII) was one of the best design decisions in C++, I think. RAII means you can't forget to clean up a resource. You don't have to type anything for it to get cleaned up at the right time, it just happens. It's basically what GC promised developers, except for all resource types, not just memory. Your code gets shorter and more correct.
I'm extremely curious to hear how this could ever be bad.
I really love how politically incorrect this post is. And how honest and fresh it sounds, though quite a few have been quick to say how wrong he is. They could be right, but I think it misses the point.
Yes, it's way too easy to do dumb stuff in C++, when you're tired or not sure what you're doing. Things like holding raw pointers or references to things you shouldn't like std::vector::data(), or questionable reinterpret casts and many other things. The compiler won't stop you, only your experience.
But he's right about one thing at least: Programming should be fun!
All these layers and rules and concerns about memory safety and security don't offer only advantages. They also have tradeoffs. And it's the same thing with those scrum agile ceremonies. It serves its purposes. But it's also the best invention ever to suck all the joy out of programming.
I think that both C and C++ still have that fun feeling going for them. When you know what you want to work on and how to do it and you just start doing it and get into the flow. And if you're careful and do things right, it just works and it's a blast!
That's my takeaway from the article. That feeling like you're talking directly to the machine, getting it to show you on screen what you saw in your mind, without anything else getting in your way. Now, that is fun!
Programming should be fun. I don't think that's controversial or politically incorrect. (Or if it is, I don't know why.)
A lot of people don't naturally have the kind of fun in C++ that Zed describes, and it seems most people here (including myself) would rather talk about that.
It's controversial because of how he makes fun about the people using and promoting other languages, like Rust. I thought it was hilarious.
And in case you haven't noticed, C++ isn't seen in a good light anymore for some years now. There are a lot of loud voices saying its time has past and calling for it to be replaced with something newer and better.
It's all subjective anyway, but I resonate with the feeling of fun he describes when doing projects in C++. Feeling productive and being protected from whole classes of bugs common in C++ is all well and good, but he was talking about programming being fun and I do not get that same feeling when programming in other languages.
It's perfectly understandable that you don't feel the same way though. I hope you do when programming in your favourite language. Otherwise it becomes just something you do to pay the rent.
Not sure about what you mean by "fun" but Peter Gottschling's Discovering Modern C++ An Intensive Course for Scientists, Engineers and Programmers is pretty good. For a catalog of shiny new C++ features Marius Bancila's Modern C++ Programming Cookbook is comprehensive.
I'm of two minds when I see comments complaining about header files. Practically speaking, I think "have the preprocessor copy & paste source files together" is a bit of a hackjob, but, conceptually speaking, having your interface and implementation separate is ultimately a good thing.
The problem of course lies not with header files, but C++ the language, as all public fields and private fields must be specified in the class declaration so that the compiler knows the memory layout. It's kind of useless in that sense. You can move private methods out to a separate source file, but, you don't gain much in doing so, at least in terms of strict encapsulation. And of course, if you use templates at all, you can no longer even do that. Which is its own can of worms.
Unfortunately, none of these problems are problems that modules solve. Implementations very much disagree on interfaces vs implementations, precompiled vs simply included, etc etc. In my own usage of modules I've just found it to be header files with different syntax. Any API implemented via modules is still very leaky - it's hard to just import a module and know what's truly fair for application usage or not. You still ultimately have to rely on documentation for usage details.
At the end of the day I don't really care how the implementation puts together a particular feature, I care about how it affects the semantics and usability of the language. And modules do not really differ in proper usage from headers, even though the whole backend had to be changed, the frontend ends up being the same. So it's net nothing.
All said and done, when it comes to defining library APIs, I prefer C. No public/private, you just have some data laid out a particular way, and some functions to operate on it. The header file is essentially just a symbol table for the binary code - and said code can be a .c file or a .o file or even a .a or .lib or .dll or whatever - C doesn't care. Raw functionality, raw usability. No hoops.
I’m not for sure auto is an improvement. I know it is required for lambdas and it makes it easier to type out a very verbose type, but it really does reduce code readability.
I’ve even seen developers use it instead of bool, which is pretty laughable as the they are the same number of characters.
A verbose enough type - and C++ has plenty of those - is indistinguishable from line noise.
There are places where having an explicit type annotation can improve readability, places where it harms readability, places where it doesn’t make much difference one way or another. Giving us the option has been a blessing. All programming calls for good taste, C++ programming calls for it more than most.
Auto is an improvement for C++ only because of its uniquely unergonomic type system and standard library. I'd very much prefer writing something like `iterator<auto>` instead of `auto` or `std::map<lotsofchars>::iterator` and not be told by every linter to change most explicit type declarations to `auto`.
How about not specifying the type, and letting the compiler infer it correctly and error out when it cannot - like so many other languages do? And those languages are much stricter about types than C++.
And auto reducing code readability? Having to figure out the intricacies of a detailed type to write was a huge barrier, and virtually anyone reading the code with a type involving several nested angle brackets would not bother mentally parsing it anyway.
I think it does reduce readability in some scenarios.
For instance:
const auto& processes =
getCurrentlyRunningProcesses();
for (const auto& process: processes) {
// Ok, what do I do with process now? Is it a pair from a map? A struct from a vector?
// If it's a pair from a map, is the key the pid, a unique id, something else?
}
std::unordered_map<Pid, ProcessData> is more readable than auto here IMO: you don't need to open the definition (or hope your IDE correctly display the type).
I remember reading something here recently about auto causing some painful and difficult to diagnose bug - I think string was what they thought the type should be (and some implicit cast would have made it a string if the type was specified)... but instead it created a string_view which went on to be used somewhere that accepted both string and string_view and then something tried to use it later but whatever the string_view was pointing to was gone (or something in that vein - I don't recall exactly).
1. Build and run it with a shell script (because the build systems do suck)
Dependencies may complicate this, but you can still link with them with a shell script
And use Unix – I started with C++ on Windows, and that sucks (also mentioned in the article)
2. Turn on address sanitizer in development, which makes it memory safe – you get a Python-like
stack trace on errors instead of undefined behavior
I use an actual editor, and unit tests, but essentially shell is my REPL for C++. It’s easier to figure out that way.
Newer features like constexpr have subtle rules, so it’s easier to just try it (even though I’ve used C++ for many years). I run all the tests with Clang too.
---
Example with ASAN:
$ echo 'int main() { char buf[1]; buf[1] = 42; }' > error.cc
$ c++ -fsanitize=address -o error error.cc && ./error; echo $?
==118199==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffcd94eb431 at pc 0x56217647520f bp 0x7ffcd94eb400 sp 0x7ffcd94eb3f8
WRITE of size 1 at 0x7ffcd94eb431 thread T0
#0 0x56217647520e in main (/home/andy/git/oilshell/oil/error+0x120e)
#1 0x7f7928446249 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
It’s not too hard to learn to read the output, and then basically you can go nuts like C++ is Python. For a small program, the edit/run cycle will be extremely fast, like Python.
The silent undefined behavior is a big barrier to learning, and this removes most of it. (You can also pass -fsanitize=undefined for UBSAN, which finds other bugs, but many fewer IME)
ASAN is built into compilers; you don’t need to install anything. A bare Debian or BSD system has all this good stuff :-)
The problem with any language trying to replace C++ for larger codebases is that it's not half as powerful as C++.
I've often cursed in C# because something that could be done trivially in C++ if impossible and causes the dev to create convuluted C# while it could be trivialy done in C++ due to its very expressive language features.
Those 0.1% of the time that you need those extreme features are what makes or breaks the language in PRODUCTION.
I've had the exact same experience, but opposite. Tons of things that are trivial in C# take a ton of code in C++ to me. Maybe it's just going from being an expert in one language to a newer language?
This often comes from expecting C# to be just like C++, where-as more complex use cases are often expected to be done in a (sometimes completely) different way there. It's a good idea to try not to fight the language and work with the way it exposes its features.
My experience was just like yours - easy to move between C and C#, or Rust and C#. But attempting C++ implementation was always far more difficult. It was never worth it over just spending extra effort in either alternative.
If GP reply author has C#-specific questions I'd be happy to answer or point him or her in the right direction. C# is a language with strong systems programming capabilities but has its own learning curve.
Zig not having operator overloading makes it suck horribly for writing any kind of vector code. If everyone had to write int a = int_add(int_mul(3, 7), 2) etc there would rightly be a riot, but since they're not 3D coders they just don't give a shit. Too bad, Zig looks great.
Sorry, one more thing to add to this: Andrew Kelley is obviously a genius, and his talk introducing Zig[0] is in my top 10 of all time technical presentations, for many reasons. But I really do wish someone close to him with a passion for how coding is in many ways applied mathematics, would ask him to please have broader algebraic support for basic operations like +, -, * and maybe divide, with their basic dataflow characteristics. Optimal speed for complex numbers vs std::complex out of the box would be attractive.
I understand his point about not wanting to allow every random C++ feature, but in these cases, it isn't a C++ feature, it's language-level basic algebra.
In C++ land, ISPC[1] is often what you use when you want top speed rendering perf on SIMD CPUs, e.g. Moonray project[2]
Final edit sorry: in the end I love C++ and have been learning Rust mainly out of curiosity. Avoiding C++ quirks one can have few problems and a great time.
I returned to C++ after 20 years to program ESP32 microcontrollers. The Arduino Framework is compiled with C++ 21... wait, what? Well that's the value of __cplusplus = 202100L
Glad to find the standard library progressed so much to make C++ more similar to modern languages, but sometimes I wish there was a simpler way to access the std namespace. Any proposal to replace "std::" with $
I was C++ dev for 5 or 6 years, up to the late 2000s.
I got another C++ job about 3 years ago but bailed after about a year.
I could write a tome about what I dislike but to start with, any language that lacks a working standard built-in string type, is just a hard no for me at this stage in my life. Life is just too short.
The tooling and IDE support is atrocious, no standard dependency management for 3rd party libraries and CMake makes maven look well designed.
I tried to pull my knowledge up to date. Hmmm, we used to have lvalues and rvalues, what's this prvalue thing?
Surely cppreference can explain:
> a prvalue (“pure” rvalue) is an expression whose evaluation
> - computes the value of an operand of a built-in operator (such prvalue has no result object), or
> - initializes an object (such prvalue is said to have a result object).
> * The result object may be a variable, an object created by new-expression, a temporary created by temporary materialization, or a member thereof. Note that non-void discarded expressions have a result object (the materialized temporary). Also, every class and array prvalue has a result object except when it is the operand of decltype;*
> The following expressions are prvalue expressions:
> a literal (except for string literal), such as 42, true or nullptr;
> a function call or an overloaded operator expression, whose return type is non-reference, such as str.substr(1, 2), str1 + str2, or it++;
> a++ and a--, the built-in post-increment and post-decrement expressions;
> a + b, a % b, a & b, a << b, and all other built-in arithmetic expressions;
> a && b, a || b, !a, the built-in logical expressions;
> a < b, a == b, a >= b, and all other built-in comparison expressions;
> &a, the built-in address-of expression;
> a.m, the member of object expression, where m is a member enumerator or a non-static member function[2];
> p->m, the built-in member of pointer expression, where m is a member enumerator or a non-static member function[2];
> a.*mp, the pointer to member of object expression, where mp is a pointer to member function[2];
> p->*mp, the built-in pointer to member of pointer expression, where mp is a pointer to member function[2];
> a, b, the built-in comma expression, where b is an prvalue;
> a ? b : c, the ternary conditional expression for certain b and c (see definition for detail);
> a cast expression to non-reference type, such as static_cast<double>(x), std::string{}, or (int)42;
> the this pointer;
> an enumerator;
> a non-type template parameter of a scalar type;
> a lambda expression, such as [](int x){ return x * x; };
> (since C++11)
> a requires-expression, such as requires (T i) { typename T::type; };
> a specialization of a concept, such as std::equality_comparable<int>.
> (since C++20)
> Properties:
> Same as rvalue (below).
> A prvalue cannot be polymorphic: the dynamic type of the object it denotes is always the type of the expression.
> A non-class non-array prvalue cannot be cv-qualified, unless it is materialized in order to be bound to a reference to a cv-qualified type(since C++17). (Note: a function call or cast expression may result in a prvalue of non-class cv-qualified type, but the cv-qualifier is generally immediately stripped out.)
> A prvalue cannot have incomplete type (except for type void, see below, or when used in decltype specifier).
> A prvalue cannot have abstract class type or an array thereof.
Yeah, this language is loads of fun. I've worked on compilers, interpreters, implemented extended Hindley-Milner type systems, etc. so normally love reading formal language specs but this is just insane.
I recall not being very effective with C++ for years, and then someone recommended the book Large Scale C++ Software Design, and that was a big unlock.
I didn’t use 80% of what’s in the book, but just having a comprehensive way of structuring the code was a massive productivity boost. Looking back, I suspect it was less that it was “the right way”, but just that it was “a way” and most of the benefit was it kept me from overthinking and got me to work.
Later with C++11, I kept having this thought, “in Python this would be way less verbose”, and I started writing C++ that looked more like Python, creating whatever helper functions Python would have (mostly simple stuff, string handling, etc).
That was one of the most productive seasons of programming I ever had, and I still get tempted to write stuff in C++ that Python is better suited for, just because the benefit of not overthinking is that significant (at least for me).
Agree. Sure there are problems here and there but I think that overall modern C++ is likely the most versatile tool in the "compiled to native, can do anything" family
No, it's one of the worst languages I ever used. Tons of footguns and bad design choices everywhere. Too much cognitive load for less benefit than other languages.
I'm surprised the article didn't mention <iostream>. The f.fail(), f.eof(), f.flags() are confusing and verbose. Even something as simple as f.read() doesn't return the number of elements read, so you need to make a separate call to f.gcount(). And then there are all the opaque types like std::streamsize, std::mbstate_t, etc., where you have no idea how their sizes relate to language types like int/long/etc. or fixed-width types like int32_t/uint64_t/etc. https://en.cppreference.com/w/cpp/string/char_traits
> JavaScript still can't even figure out what a for-loop is
ECMAScript 6 added the for-of loop, which is the more useful alternative to the for-in loop.
> C++ has lambda, and it's not bullshit like Python's lambda
C++ lambdas have a heavier syntax than any other lambda I know of (e.g. Python, Java, JavaScript, Haskell, Rust), because it needs to specify attributes and captures. https://en.cppreference.com/w/cpp/language/lambda
> My thinking is C++ is now about as good as any other language out there
Not by a longshot. Instead of C++, I reach for Java if I want fast design time, safe operations, and a more limited set of tools (e.g. not needing to decide how many layers of pointer indirection I want). I reach for Rust if I want the power of C++ without its footguns.
Heck, my motto for Rust has always been, "C++ done right". Every time I compare analogous features in C++ and Rust, I find that the Rust version is much better designed. As the simplest example, in Rust it's a compile-time error to use a variable whose value is moved out, whereas in C++ the variable is still usable but has an invalid value. Another example is that Rust has traits but C++ relies on instantiating templates and then "duck-typing" to see if the resulting code can actually compile. And let's not forget nullptr, the trillion-dollar mistake - C++ makes nullptr implicitly part of every pointer(*) type, but Rust bans it by default unless you opt in with Option<T>. Rust has other quality-of-life features such as easily declared tuple types, the unit type instead of void (which makes functional programming easier as you don't have to special-case void), pattern matching and unpacking, methods on primitive types (e.g. 456u32.isqrt() instead of sqrt(456)). I just can't look at C++ seriously when Rust is miles ahead, being more expressive and safer.
> The Amazing Comeback of C++11
I will agree with this in a limited sense When I write C++ code (because I'm a masochist), I will not tolerate anything less than C++11, because C++03 and C++98 are much, much worse. I'm talking about things like various types, standard library classes/functions, unique_ptr, and move semantics.
I'm mostly very much in agreement with what you've said here but I want to pick on a few things:
> Instead of C++, I reach for Java if I want fast design time, safe operations, and a more limited set of tools (e.g. not needing to decide how many layers of pointer indirection I want).
I don't think I've ever seen a good reason to prefer Java over C# for anything.
> Another example is that Rust has traits but C++ relies on instantiating templates and then "duck-typing" to see if the resulting code can actually compile
>the unit type instead of void (which makes functional programming easier as you don't have to special-case void)
Why would special-casing be necessary? You don't need to say e.g. that mapping a void-returning function produces an empty result; it could just be a compile error. I feel like void returns should be a special case and I don't like all the ways `None` is used in Python, because it's one of the few things that blurs an otherwise very strong distinction between statements and expressions, analogously between commands and queries.
> in Rust it's a compile-time error to use a variable whose value is moved out, whereas in C++ the variable is still usable but has an invalid value
C++ does it this way because there are common cases in systems code where doing it the Rust way would literally be unsafe. Not all memory references are visible at compile-time and may exist outside the address space.
Would you mind elaborating more on those common cases? I'm not sure I've heard of destructive moves being less safe than non-destructive moves and I'm not smart enough to figure out what you're talking about in your second sentence.
Shared address space. Some other process or silicon can read or write the object you just moved but doesn’t know you moved it. You need to keep the memory previously occupied by the moved object valid long enough for those references to realize you moved it to prevent corruption.
A typical case is high-performance I/O, which uses a lot of DMA. DMA is oblivious to most programming language semantics like lifetimes, ownership, etc and will happily step all over your address space if you aren’t careful.
I'm curious to hear more about this use case. The DMA I do in rust is generally static buffers, because I'm not sure how to pass the borrow checker otherwise. (There are ways). Generally, you set up a static [u8] buffer, and pass its pointer to the hardware that's doing the DMAing. Then magic, then the buffer gets read or written by the hardware. In this scenario, the variables never go out of scope. Am I cheating, and avoiding this issue by using static buffers? If the buffer drops during a DMA transfer, I believe UB happens.
I'm suspicious a similar principle happens with memory-mapped flash memory as well, e.g. QSPI.
> Some other process or silicon can read or write the object you just moved but doesn’t know you moved it.
That should primarily affect buffers that are inline with the moved object, right? i.e., not static buffers or stuff that's heap-allocated? How common is that scenario? I admittedly generally thought DMA used static buffers, though to be fair I'm not exactly highly experienced in the space.
> You need to keep the memory previously occupied by the moved object valid long enough for those references to realize you moved it to prevent corruption.
How is this (reliably) handled in C++? I feel there's gotta be more than just hoping the empty object hangs out long enough for the rest of the system to catch on (e.g., moving things around near the end of a scope when the empty object will be destroyed "soon").
This just means that affine types aren't the right tool to model memory that you don't have full control over. Which is true, but also represents a very small subset of overall data. Rust provides you with other tools to handle those kinds of situations.
There is a small wart here, which is that (with async Rust) some of these use cases would benefit tremendously from full-fledged linear types, or at least an easy way to run code during async cancellation.
The difference between an affine and a linear type is that the ways in which a linear type is consumed are controllable through encapsulation — for example, imagine you have a type which represents a certain amount of money, and you want to statically prevent the money from being dropped on the floor. Affine types don't prevent that statically, but linear types do. You can still have runtime checks though.
> No, it's one of the worst languages I ever used.
That says more about you than the languages you've used.
C++ is one of the top 5 languages used in production. This is true still today, with so many specialized languages to pick and choose. No one had to hold a gun to anyone's head to get them to adopt it. How do you rationalize that if your opinion had any substance or merit?
For the sake of argument, I assert exactly the opposite: C++ post-C++11 is the absolute best language ever devised by mankind, bar none. Am I wrong?
> Tons of footguns and bad design choices everywhere.
Please go ahead and point out the single most egregious "foot gun" or bad design choice you can possibly imagine. The worst. This will serve to show the world how well thought through your opinion actually is.
I don't think C++ is one of the worst languages; there are very few languages as powerful as C++, that alone makes it one of the best.
But, much like love and hate, I also don't think that the opposite of good is always necessarily bad, nor vice-versa. A language can be both good and bad at the same time, in different aspects.
C++ is really good (unrestrained freedom, performance, ecosystem), and also really bad (tooling, templates, really hard to debug memory issues).
Rust is somewhat less good (less free, slower, puny ecosystem in comparison), but also a lot less bad (powerful type system, thread safety, fearless iterators/lambdas, etc).
Many of the warts C++ has to carry due to its commitment to compatibility, are fixed in Rust with much better alternatives. A lot of footguns are well encapsulated in Rust's affine-ish types and algebraic data types, while still providing unsafe hatches for when you need them. Defaults really matter.
I had a similar reaction returning to C++ a few years ago, and getting exposed to stuff like lambdas, smart pointers, and template metaprogramming painpoints being fixed. One especially nice thing about C++ is GCC, which lets you use relatively recent features even on obscure HW/OS architectures that other, trendier languages ignore. For example, you can use modern C++ features, at least upto C++17, on OpenBSD.
>But, C++ kept evolving, and the standards committee seemed to realize that if they don't do something they'll become an obscure language only used by a single 10 trillion dollar industry. How pathetic!
The stuff you use for work is not fun. The stuff you use for fun is fun. There, I solved the mystery for you.
If you choose technology for work by what is the most fun - you enter a hedonist treadmill. Stop. JS framework insanity lies that way. No cool technology will save you from burnout.
The amount of high performance, production grade, massively tested libraries written in C++ is unbeatable. I will be honest here, it's easier to improve C++ security by implementing a compiler that produces safer C++ (like Typescript to Javascript) than rewriting everything in any other language (Rust, Zig, Odin, whatever).
I mean, could you estimate the cost ($ and time) it would take to rewrite the best audio framework in any other language? (https://juce.com/).
Zig is great, it’s not aiming to be a replacement for C++ though. One of the awesome things about Zig is its interoperability with C. In that sense it’s more of a Typescript to C than Rust is to C++. I’m still not sure what I think about Rust personally. In my region of the world I suspect it’ll continue to struggle to find any form of adoption as the C++ people seem to have very little interest in trading their decade long experience for Rust. Zig on the other hand is winning the hearts and minds of most C programmers I know.
I hope Rust succeeds though. I say this more from a change management perspective than anything else. It’s extremely hard for us to find developers who will primarily work with garbage collected languages but occasionally have to work with either C or C++ when bottle necks appear. Rust makes that much easier, or perhaps less dangerous would be a better term. I’m not sure any of the attempts at making C++ more safe to use is going to really succeed in this regard. Maybe, but I nothing within the C++ community seems to pull in that direction so I doubt it. I’d like to mention that I’m aware that Zig isn’t helpful in this regard either as it’s not memory safe.
How many total lines of code do you imagine are in these libraries, compared to all their clients? If rewriting the libraries sounds like an unreasonable amount of work in a world where all that client code exists, doesn't that reflect negatively on the readability of C++?
I am a bona fide C++ hater, but that's just because I resent working on it for professional code that needs to work reliably with coworkers I don't entirely trust. For personal projects it's one of the most satisfying coding contexts I've ever worked and it is indeed a blast to have such fine-grained control over execution.
I abandoned the language about a decade ago and I don't see myself looking back. My projects these days need long-term reliability more than anything and rust + cd/ci hits a sweet spot. That said, I do miss the thrill of designing and executing a program that runs in a certain way exactly as I intended and knowing that it was my expertise and insight that allowed this execution. Would I want to work with someone that was driven by that? Hell no! But it is personally a joy I won't forget.
Other inexcusable pain points: the build systems and package management is an absolute nightmare; the pre-processor feels like a sadistic joke; the syntax is horrible; there's so much cruft in the runtime you need years of experience to not machine-gun your foot off by using the most obvious tool at your disposal. But in a sense this just increases the joy of shipping a working executable with all your cleverness and blood and tears wrapped with a bow.
Kudos for one of the most relatable descriptions of C++ I've read.
I did a couple years writing C/C++ professionally, and I hope to not go back to that. Too many hours debugging other people's code, suffering vague integration issues, and just trying to get the build system spaghetti to run.
I just spent 3 days to statically link a third party dependency to my C++ project, in a way that works with linux/windows/macos. The package wasn't available on conan/vcpkg, it was just a github repo with a weird combination of Makefile/cmake file. While I'm far from a cmake/cpp expert, this is a non-issue with most modern languages: you just pip install, cargo add, npm install, go get etc. You can read front-to-cover books about every details of C++ semantics and it'll still be a PITA to work on real world projects
What was particularly difficult about it? Sometimes modifying someone else's code to build statically or on another platform is tricky. Linking is generally just include dir, lib path, and lib though.
The library was duckdb (https://duckdb.org/docs/installation/), and here are some of the issues I ran into:
- trying to compile against the prebuilt duckdb_static.so file got me a ton of undefined reference error, I asked about this on their discord and it seems to be a knwon issue, there are additional dependencies that are not part of the release so I had to build it myself
- the library uses CMake but it contains a Makefile and they recommend using that to build it
- It seems like on windows, if you create a dynamic library you have to add __declspec(dllexport) before each function. DuckDB has a #ifdef DUCKDB_STATIC_BUILD to toggle/disable it which isn't documented anywhere, you have to read the code
I'm sure someone will tell me that this is very standard and shouldn't take me 3 days to figure it out, but with basic knowledge of cmake and build systems that's a bit of a pain, and there's no other language where you have to do that
CMake, the defacto if not standard build system, when correctly setup, does all this for you.
Your argument is along the lines of "the maintainer of X rust package didn't setup cargo correctly and it's impossible to include in my project, rust is a horrible language". You can absolutely make an argument that the lack of a standard build system is a pain point, and well it's C++ you get to be a special snowflake and use a makefile instead of CMake but then the onus is on you to provide correct documentation.
In the case of duckdb they provide proper packages for pip, cargo, npm, go get, and the only one that seems like a PITA to use is the C/C++ dependencies, even though the package is written is C++, so I think this tells something about C++ dependency management
You seem to be agreeing with me then, weird take to do it while sounding like you disagree...
As I said, you can make the argument C++ build systems are no good. However the argument originally made is that C++ the language caused these issues when in reality the package maintainer hasn't put in the effort to properly document and fix known issues in their chosen build system for C++.
The fact that they in fact have built out proper packages for pip, cargo, npm and go tells me they have the necessary expertise on the team and made an active choice to not do the same for C++. Create an issue with the maintainer.
The argument originally made was "You can read front-to-cover books about every details of C++ semantics and it'll still be a PITA to work on real world projects"
Which is not disagreeing with you just because you are explaining/disputing the reason it was a PITA.
Seeing as it's Christmas Eve and I has nothing better to do...
Duckdb own documentation says the C++ API is internal and you should use the C API[1]
The makefile also has a 'bundle-library' option which seems to be exactly what you were looking for, it generates a statically linked library which is what the Golang package is using, probably others too but that's the one I checked first.
This is purely a documentation problem, the build system does what is needed it seems. Create an issue, or better yet, add the necessary documentation...
1. https://duckdb.org/docs/api/cpp
I used the C api but I did miss the 'bundle-library' option, will look into that
I definitely don't blame you for missing it.
I found it by looking at the go package because they claimed they are statically linking there.
> the maintainer of X rust package didn't setup cargo correctly and it's impossible to include in my project
That basically never happens, which is the point.
The javascript toolchain for dependencies and build is as arcane as the sloppiest Makefile.
It's amazing that we ended up with the javascript equivalente of autotools.
Created an account just to say: I am a 10-year JavaScript veteran who knows the ecosystem at least as well as any sane person could possibly be expected to, and this is 100% correct. When I tell people that JS build tooling is the second-worst headache to C/C++ build tooling, they laugh, but stick a toe outside the happy path of `npm create <some-framework-project> && npm start` and you will know what pain feels like.
I did get some similar vibes trying to work with Gradle and Maven for a project a while back, but I don't really live in that world so it's hard to say whether my experience was typical or just symptomatic of my inexperience.
And it'll continue to get worse because JS devs love coming up with their own abstractions over everything and piling complexity. You don't write JS, you write TS. You don't write HTML, you write JSX. You don't write CSS, you write Tailwind. And of course, React has its own compiler as of recently. Now you have to figure out in what order to run these parsers, bundle everything together including your hundreds of dependencies, minimize, obfuscate, tree shake, and whatever else fairy dust magic you want to sprinkle on top. Meanwhile, the default build tool of choice changes about once every 2 years, and now you also have a choice of runtime - node vs bun vs deno. Can't wait to see what the next revolutionary idea would be to contribute to this madness.
There are those of us out there that have a beautiful experience with JavaScript. Vanilla JS, web components, a lightweight rendering library like lit-html, class props as reactive state...
NodeJS + express + vanilla web components is the most graceful and productive stack I've ever used in 30+ years of development.
yeah, dealing with npm really made me feel like the best part of autotools
But to flip this around, let's say you want to use this third party in some other "modern" language how would this become better?. Clearly if someone hadn't done the work to package in vcpkg/conan you can expect it wouldn't exist in pip/cargo/npm either. So if you had a bad time in C++, you would have a dramatically worse time in other languages. Goes to show what everyone else is saying here, there are parts of historic C++ which are in very bad shape particularly the build story. However, if it's in vcpkg it is exactly as easy to install as the other ones.
> But to flip this around, let's say you want to use this third party in some other "modern" language how would this become better?. Clearly if someone hadn't done the work to package in vcpkg/conan you can expect it wouldn't exist in pip/cargo/npm either. So if you had a bad time in C++, you would have a dramatically worse time in other languages.
Err, no?
In case of Rust usually there is really no work that's necessary to package it and upload. You just type `cargo publish` and you're done (after filling out metadata in Cargo.toml). Even if someone wouldn't upload it to crates.io as a Rust crate you can just add a single line to your Cargo.toml:
and you're done, you can use it as a dependency.I think you moved the goalposts. I can configure a CMake file to pull a GitHub repo, which seems similar enough.
I think the difference is that you can pip install a github url and it will work, it will even work if the upstream has done nothing to package it.
I think the issue could be broadly defined as how difficult it is how difficult it is to take someone else's code and use it as part of your project. For C/C++ the answer can be completely trivial to a massive pain in the ass.
Oh I dunno. With cmake it really is pretty straight forward. You can pull from VCS, a tarball, even use a git submodule.
If it's another Cmake project, that's the happy path. Easy peasy. If it's not you still have the ability to run ./configure && make or evoke whatever incantation is in the dependency's requirements. I'm not the hugest fan of cmake, but I've worked with it quite a bit and it's a much more pleasant story than other languages I've used a lot like python, JavaScript, OCaml (which I love), etc.
I do find rust and go dependency management a little simpler due to the prescribed nature of them, but once you get off the happy path, the flexibility of C land is tough to beat. It sort of matches the language stories themselves; you have more control and have to do a bit more yourself. It's all about trade offs.
How often do folks venture off the happy path with Rust dependencies? Personally I’ve never once found myself in that situation.
I've done it before to integrate rust into polyglot build systems. There's a surprisingly long history of build systems trying and failing to implement rust builds without the "happy path" of simply wrapping cargo. As far as I know no one's ever succeeded.
The main difference being that Cargo is both for distributing and building locally AND it’s the blessed way of building all Rust projects. This means that as long as you have a copy of the codebase, it’s going to be using Cargo & you can add it with Cargo regardless of it being published formally to crates.io (the copy can even be remote since you can build from a git reference directly). Python packaging requires more work although there’s typically no build step so you can probably just __import__ the path and move on. JS is closer to Rust in that package.json is the standard way to manage components and you can import them even if they’re not published via the npm registry.
Pretending the situation isn’t very different for C++ is completely ignoring the standard committee’s complete abdication of standardizing a build system (which by the way if they’re not going to do, why are they bothering to standardize anything?)
This article seems kinda confused. It makes a lot of points, but I'm struggling to extract why that means C++ is more/less fun.
The simultaneous description of modern C++ as "extremely high quality" and yet pervasive legacy bs, horrible tooling, etc is confusing. They say unique_ptr is great yet they "really hate RAII". Many of the discussed topics are largely irrelevant or incorrect. Metaprogramming used to be everywhere but now isn't (what?? Take a look at concepts)? C++ has the best graphics libraries (surely not true)? Installing Python is harder than C++? These seems like odd obsessions of the author, with little relation to the argument.
I think the author just finds C++ fun for some complex combination of personal factors, which is fine. Perhaps partly due to a "hacker" type personality. Some of the most fun I've had with programming has been C++, due to its performance and technicality. At the same time, C++ is in practice crap for "real" software development where your own short-term enjoyment is not paramount (for a billion reasons we all know). C++ is a deeply conflicted language, scarred by decades of legacy and politics. Ignoring the reality of C++ is the biggest mistake of those who discuss it. My attitude towards C++ these days is of tiredness. I don't want to jump through the hoops C++ has thrown at me for the past many years. We don't need to beat an already long dead horse. It's ok to let C++ be what it is.
> I think the author just finds C++ fun for some complex combination of personal factors, which is fine. Perhaps partly due to a "hacker" type personality.
I think that sums up the point of the article just fine.
To me at least, it's not so much that it's confused as that it's him being excited about having fun and doing his best to share his joy. Which means that, sure, it's not a proper essay at all, it's not really trying to convince you to agree so much as saying "if you find this relatable without me trying to actively convince you, maybe you'd find it fun too."
I think given you've been mugged by reality in commercial work to the point of tiredness, your not finding it to make sense at all is completely understandable, though.
I'm currently having a blast writing javascript of all things, and I imagine if I wrote up a similar blog post explaining why it'd come across just as badly to somebody burned out on the last decade of commercial javascript too.
But I'm glad he's having fun, and I'm glad he decided to share the joy.
C++ really is a blast and I think the complaints people have about it really depend on context. Lots of C++ devs hate that language but imo misdirect their hate. They actually work on legacy products built by people who were never that great at writing software. This happened to me with Rust actually where I was thrust into an existing rust project at the company I work for. It's an "old" codebase with many different and conflicting conventions within it (different error handling types that don't compose well, bad class abstractions, lots of unsafe block when not needed, etc). The project was the most miserable project I've ever worked on and it's easy for me to blame Rust, but it's really just bad software development. There are warts in C++, but a lot of the pain people run into is just that they are working on crap code and it would be crap in any language.
In every other language (except C), basically every time you write v[x]=y you aren't inviting the possibility of arbitary memory corruption. The C++ 'std::optional' type makes calling '*v' undefined behaviour if 'v' is empty. The whole point of optional is to store things that might be empty, so why put undefined behaviour on the most common operation on an optional when it's empty (which is going to be common in practice, that's the point!)
The problem I have with C++ (and I've written a lot), is every programmer involved in your project has to be 100% perfect, 100% of the time.
I know you can run memory checkers, and STL debug modes, but unless you are running these in release as well (is anyone doing that?), then you are then counting on your testsuite hitting every weird thing a silly, or malicious, user might do.
Given how important software is nowadays, and how Rust seems close to the performance of C++, is it really worth using a language where so many of the basic fundamental features are incredibly hard to use safely? They keep making it worse, calling an empty std::function threw an assert, the new std::copyable_function has made this undefined behaviour instead!
The C++ 'std::optional' type makes calling 'v' undefined behaviour if 'v' is empty*
Are you talking about dereferencing a null pointer? That is going to case a low level exception in any language, it has nothing to do with std::optional.
every time you write v[x]=y you aren't inviting the possibility of arbitary memory corruption
That's how computers work. You either pay the penalty of turning every memory check into a conditional boundary check first or you avoid it all together and just iterate through data structures, which is not difficult to do.
Also debug mode lets you turn on the bounds checking to debug and be fast in release mode.
The reality is that with a tiny amount of experience this is almost never a problem. Things like this are mostly relegated to writing your own data structures.
No, I'm talking about std::optional<T>. It's a type designed to store an object of type T, or nothing. You use '*v' to get the value out (if one is present).
It was originally discussed as a way of avoid null pointers, for the common case where they are representing the possibility of having a value. However, it has exactly the same UB problems as a null pointer, so isn't really any safer.
I'm going to be honest, saying that memory corruption in C++ is 'almost never a problem' just doesn't match with my experience, of working on many large codebases, games, and seeing remote security holes caused by buffer overflows and memory corruption occurring in every OS and large software program ever written.
Also, that 'penalty' really does seem to be tiny, as far as I can tell. Rust pays it (you can use unsafe to disable it, but most programs use unsafe very sparingly), and as far as I've seen benchmarks, the introduced overhead is tiny, a couple of percent at most.
You can pay the penalty and get safe container access, too. AFAIK, C++ standard containers provide both bounds checked (.at()) and unchecked ([]) element retrieval.
Now we are getting down to a philosophical issue (but I think an important one).
In Rust, they made the safe way of writing things easy (just write v[x]), and the unsafe one hard (wrap your code in 'unsafe'). C++ is the opposite, it's always more code, and less standard (I don't think I've seen a single C++ tutorial, or book, use .at() as standard rather than []), to do bounds checked.
So, you can write safe code in both, and unsafe code in both, but they clearly have a default they push you towards. While I think C++'s was fine 20 years ago, I feel nowadays languages should push developers to write safer code wherever possible, and while sometimes you need an escape hatch (I've used unsafe in a very hot inner loop in Rust a couple of times), safer defaults are better.
You have to do a very similar operation to has_value() .value() with Rust's optional though... How is opt.ok_or("Value not found.")?; so different from the C++ type?
It seems like people think dereferencing an unengaged optional results in a crash or exception, but ironically you are actually less likely to get a crash from an optional than you would from dereferencing an invalid pointer. This snippet of code, for example, is not going to cause a crash in most cases, it will simply return a garbage and unpredictable value:
While both are undefined behavior, you are actually more likely to get a predictable crash from the below code than the above: I leave it to the reader to reflect on the absurdity of this.One is undefined behavior which may manifest as entirely unpredictable side-effects. The other has semantics that are well specified and predictable. Also while what you wrote is technically valid, it's not really idiomatic to write it the way you did in Rust, you usually write it as:
At which point value is the "dereferenced" value. Or you can take it by reference if you don't want to consume the value.I've been wondering how much you'd pay if you just required that out of bounds accesses be a no-op with an option of calling an error handler. De-reference a null or out of bounds pointer you get zero. Write out of bounds, does nothing.
I really suspect that unlike the RISC mental model compiler writers think in terms of a superscalar processor would barely be slowed down. That is if the compiler doesn't delete the checks because it can prove they aren't needed.It was originally discussed as a way of avoid null pointers,
I don't think this is true. I don't think it has anything to do with null pointers, it is a standard way to return a type that might not be constructed/valid.
I think you might be confusing the fact that you can convert std::optional to a bool and do if(opt){ opt.value(); }
You might also be confusing it for the technique of passing an address into a function that takes a pointer as an argument, where the function can check the pointer for being null before it puts a value into the pointer's address.
I'm going to be honest, saying that memory corruption in C++ is 'almost never a problem' just doesn't match with my experience,
In modern C++ it is easy to avoid putting yourself in situations where you are calculating arbitrary indices outside of data structures. Whether people do this is another story. It would (or should) crash your program in any other language anyway.
Also, that 'penalty' really does seem to be tiny
The penalty is not tiny unless it is elided all together, which would happen in the same iteration scenarios that in C++ wouldn't be doing raw indexes anyway.
>I don't think this is true. I don't think it has anything to do with null pointers
The paper that proposed std::optional literally uses examples involving null pointers as one of the use cases that std::optional is intended to replace:
https://isocpp.org/files/papers/N3672.html
Here is an updated paper which is intended to fix some flaws with std::optional and literally mentions additional use cases of nullptr that the original proposal did not address but the extended proposal does:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p29...
I am going to be somewhat blunt, but if you're not familiar with how some of the use cases for std::optional is as a replacement for using raw pointers, along with some of your comments about how C++ treats undefined behavior, as if it's just results in an exception, suggests you may not have a rigorous enough understanding of the language to speak so assertively about the topic.
I am going to be somewhat blunt, but if you're not familiar with how some of the use cases for std::optional is as a replacement for using raw pointers
I never said there wasn't a use case, I said it wasn't specifically about protecting you from them. If you put a null pointer in and reference the value directly, it doesn't save you.
If you don't understand the context of the thread start arguments over other people's simplified examples. I'm not going to write huge paragraphs to try to avoid someone's off topic criticisms, I'm just telling someone their problems can be avoided.
Yes you did and I literally quoted it, here it is, your words:
"I don't think this is true. I don't think it has anything to do with null pointers, it is a standard way to return a type that might not be constructed/valid."
This was in response to:
"It was originally discussed as a way of avoid null pointers,"
I have provided you with the actual papers that proposed std::optional<T> and they clearly specify that a use case is to eliminate the use of null pointers as sentinel values.
It's a template, you can put whatever you want in it including a pointer. All your example shows is that you can bind a pointer to a reference and it will wrap it but won't access the pointer automatically.
I'm not sure what your point here is other than to try to mince words and argue. It's a standard way to put two values together and what people were probably already doing with structs. You can put pointers into a vector too, but that doesn't mean it's all about pointers.
I'm pretty sure the issue that the parent commenter is referring to isn't about wrapping a pointer type in an optional, but wrapping a _non-pointer_ type in an optional, and then trying to access the value inside. std::optional literally provides a dereference operator operator[1] which contains the following documentation:
> The behavior is undefined if this does not contain a value.
The equivalent to this in Rust isn't `Option::unwrap`, which will (safely) panic if the value isn't present; the equivalent is `Option::unwrap_unchecked`, which can't be invoked without manually marking the code as unsafe. I've been writing Rust professionally for a bit over five years and personally for almost ten, and I can definitively say that I've never used that method a single time. I can't say with any amount of certainty whether I've accidentally used the deference operator on an optional type in C++ despite writing at least a couple orders of magnitude less C++ code because it's not something that's going to stick out; the deference operator gets used quite often and wouldn't necessarily be noticeable on a variable that wasn't declared nearby, and the compiler isn't going to complain because it's considered entirely valid to do that.
[1]: From https://en.cppreference.com/w/cpp/utility/optional/operator
Is your argument that rust with panic if you are a bad programmer and C++ says its Ub of you are a bad programmer?
That's just fundamental difference of opinion, Rust isn't designed for efficiency, it's designed for safety first. C++ unofficial motto is, don't pay for what you don't use.
If I type *X why would I pay for a check if its empty, I literally should have checked the value isn't empty.
If you work in a code base with people who don't check, your codebase doesn't have static analysers, you do no code review and dereferencing an uncheck optional get's to production, do you think a .unwrap in rust wouldn't have made it to production?
> "Rust isn't designed for efficiency"
Citation needed, because Graydon Hoare the original Rust creator (who has not been involved with Rust development for quite a long time) wrote about how the Rust that exists is not like the original one he was designing:
- "Tail calls [..] I got argued into not having them because the project in general got argued into the position of "compete to win with C++ on performance" and so I wound up writing a sad post rejecting them which is one of the saddest things ever written on the subject. It remains true with Rust's priorities today"
- "Performance: A lot of people in the Rust community think "zero cost abstraction" is a core promise of the language. I would never have pitched this and still, personally, don't think it's good. It's a C++ idea and one that I think unnecessarily constrains the design space. I think most abstractions come with costs and tradeoffs, and I would have traded lots and lots of small constant performancee costs for simpler or more robust versions of many abstractions. The resulting language would have been slower. It would have stayed in the "compiled PLs with decent memory access patterns" niche of the PL shootout, but probably be at best somewhere in the band of the results holding Ada and Pascal."
https://graydon2.dreamwidth.org/307291.html
Your basis seems to be no-one is ever going to write bad code, anywhere, ever, and invoke undefined behavior. That doesn’t seem reasonable.
Also, an unwrap isn’t perfect, but it’s much better than UB. It asserts. No memory corruption, no leaking all your user’s data, no massive fines.
The equivalent to C++ would be an unchecked unwrap in an unsafe code block, and that would throw up flags during review in any Rust codebase.
An unchecked dereference should also throw up flags during review in a C/C++ codebase. I didn't assume that nobody would make mistakes. My argument has always been that you use a language like C++ where needed. Most of your code should be in a GC language. Going in with that mentality, even if I wrote that code in Rust, I'm exporting a C API, which means I may as well have written the code in C++ and spend some more time in code review.
EDIT: an unwrap that crashes in a panic is a dos condition. In severity this might be worse or better depending where it happens.
Both are programmer error, both should be caught in review, both aren't checked by the compiler.
It's okay to admit you were wrong.
I think if you had something real to say here you would have done it already.
I just want to pick out one thing:
It would (or should) crash your program in any other language anyway.
To me there is a massive difference between "there is a way a malicious user can trigger an assert, which will crash your server / webbrowser", and "there is a way a malicious user can use memory corruption to take over your server / webbrowser".
And bounds-checking penalties are small. I can tell they are small, because Rust doesn't have any kind of clever 'global way' of getting rid of them, and I've only noticed the cost showing up in profiles twice. In those cases I did very carefully verify my code, then turn off the bounds checking -- but that's fine, I'm not saying we should never bounds check, just we should do it by default. Do you have any evidence the costs are expensive?
If you profile looping through an array of pixels in linear memory order vs bounds checking every access it should be more expensive due to the branching.
As someone else mentioned you can boundary check access if you want to in C++ anyway and it's built in to a vector.
My point here is that it isn't really a big problem with C++, you can get what you want.
Iterating over an array in Rust does not involve bounds checking every access since Rust guarantees immutability to the container over the course of the iteration. Hence all that's needed is to access the size of the array at the start of the iteration and then it is safe to iterate over the entire array.
Note that this is not something C++ is able to do, and C++ has notoriously subtle iterator invalidation rules as a consequence.
They asked for evidence of bounds checks being expensive, please try to keep the context in mind.
Note that this is not something C++ is able to do
Or you could just not mutate the container.
and you haven't given evidence of bounds checks being expensive. They didn't say it was free, they said "bounds-checking penalties are small". You've described a situation where bounds checking happens and simply declared that it "should be" expensive.
It's the optimization that C++ is unable to perform, not the immutability.
If you declare using an invalidated iterator as UB, the compiler can optimize as if the container was effectively immutable during the loop.
Are you talking about optimizing out bounds checking that isn't happening in the first place?
I really can't resonate with this. The type system is obnoxious (and you don't even get proper memory safety out of it), the "most vexing parse" truly is vexatious, there's all the weirdness inherited from C like pointer decay (and then building new solutions like std::array on top while being unable to get rid of old ways of doing things), and of course the continued reliance on a preprocessor to simulate an actual module import system (and the corresponding implications for how code for a class is organized, having to hack around with the pimpl idiom etc....)
Essentially, the warts are too large and numerous for me to find any inner beauty in it any more.
> and of course the continued reliance on a preprocessor to simulate an actual module import system
C++ modules are supposedly meant to save the day. Although only recently have the big three compilers (GCC, Clang, MSVC) reached some form of parity when compiling modules.
I mean, this is all anecdotal, but I've only ran into the most vexing parse a few times, I rarely had to use the pimpl idiom, and the header file stuff... okay that isn't great. I've been a c++ dev for 10 years and I actually worked on an extremely old codebase and was involved in modernizing it is to C++11. Maybe I'm too C++ brained, but all those things just aren't that bad? There is no wartless language and you just deal with the warts of the language you're in and it's your responsibility to learn the details of the language.
The header stuff is pretty bad. The rest are strange choices of stuff to complain about. The vexing parse was never a big deal and these days with bracket initialization (which only rarely you can't use) even less. The pimpl idiom: not sure why it's really a problem. Plus you have many choices for type erasure in c++.
>The vexing parse was never a big deal
I definitely got bit by it multiple times.
>The pimpl idiom: not sure why it's really a problem.
Because it's even more boilerplate and adds indirection in a place where you might have been painstakingly trying to avoid it (where the first indirection costs you all your cache coherency). C++ lets you go out of your way to avoid objects having to carry any overhead for virtual dispatch if you aren't going to use it; but then you might have to choose between rerouting everything through a smart pointer anyway or suffering inordinately long compile times. Nothing to do with type erasure (if I'm thinking clearly, anyway).
I can deal with all the warts but I'm not really having a blast.
[dead]
True - but it’s much easier to make an incomprehensible mess in more complex languages. Whereas blub language projects are pretty easy to decipher regardless of the state of the codebase.
This 1000%, stop blaming the tools!
How can we make better tools if we don't blame tools?
Eh, I'm reminded of the old "programming languages as weapons" comic, with the one that I still remember is JavaScript was a sword without a hilt, with the blade being the "good part" and in place of the hilt, another blade labelled the "bad part".
You can blame devs instead of tools all day long, but you can't deny that there are things about the tools that hold the developers back.
I periodically work on c++ instead of C and each time it follows a similar pattern where I learn about some new c++ trick, think it will make things better, write my code, hit a compiler error, then spend the rest of the day learning why I can't do what I want. Granted, I usually am stuck at c++14, and some of the issues are fixed in future versions but still...
I really want to love C++. It gives me a more powerful C that theoretically should improve my output but in the end it carries so much cognitive baggage that I usually end up writing "C with classes" and move on.
> then spend the rest of the day learning why I can't do what I want
There's a point when learning's fun, I think the OP is still there.
I wrote a bunch of realtime C++ in 2003, hated it. But last year, I wrote most of my code in C++ and liked it finally.
Lambda and auto was the tipping point where I stopped hating it. Some templates thrown in, but mostly to avoid writing a lot of branches for variants of the same function.
With lambdas I could now write the way I was initially taught programming, mostly by Lispy teachers channeling SICP.
Didn't hate the allocations either with unique_ptr.
If you haven’t already look up ‘if constexpr’ in cpp17 and newer. It lets you have compile time branches in a single function.
Also requires, which initially looks batshit insane but is in fact really quite cool: https://www.think-cell.com/en/career/devblog/if-constexpr-re...
"Lispy teachers channeling SICP" made new audibly laugh
> There's a point when learning's fun, I think the OP is still there.
What a horrible idea that one might no longer be there.
I see a lot of bad code from people who have just learned about some feature and want to use it. Don't do that. Think of how to write your code, and then, if a feature solves a problem, use it. Problem->Solution, not the other way around.
E.g. C++ templates are generally pretty awful, but sometimes, compile-time duck typing, or automatically adding padding based on sizeof(), etc, is very useful.
C++, like most technology, works better if you put in the work to learn about it.
(Admittedly there are languages like python and ruby that buck this trend.)
Sure, but the effort/reward ratio bears some consideration. I've put a lot more effort into learning C++ than any other programming language but I'd still say it's just the language I'm maybe 4th most proficient in.
> C++, like most technology, works better if you put in the work to learn about it.
There's also the impact of software entropy: if someone has little to no experience developing software and has to grow it by adding features and fixing bugs, over time their contribution to the project invariably results in degrading it beyond the point they can possibly salvage it.
At that point, they blame the tools.
Programming languages are a means to an end. I'd rather have my cognition going towards solving the actual problem and not worrying about implementation details.
Python does work better if you put in that kind of work. Otherwise you'll get bit by the way mutable default arguments work[0] (or never learn to use it to your advantage), or by late binding of closures[1] (and maybe you'll pick up the awful habit of exploiting the confusing early binding of default arguments to make the `lambda`s that you constructed in a loop work properly[2]). Or you won't get the big picture of the descriptor protocol[3] and how method lookup[4] is related to the implementation of properties[5]. Or you won't get the fancy metaclass[6] thing that your favourite framework is expecting you to treat as an opaque abstraction[7], or how and when you might use a class decorator[8] instead of a metaclass to do that kind of metaprogramming.
[0]: https://stackoverflow.com/questions/1132941
[1]: https://stackoverflow.com/questions/2295290
[2]: https://stackoverflow.com/questions/3431676
[3]: https://docs.python.org/3/howto/descriptor.html
[4]: https://eev.ee/blog/2013/03/03/the-controller-pattern-is-awf... (yes, this was partly an excuse to get one of Zed Shaw's critics [9] into the discussion)
[5]: https://stackoverflow.com/questions/3798835
[6]: https://stackoverflow.com/questions/100003
[7]: e.g. https://medium.com/@miguel.amezola/demystifying-python-metac... - I didn't have a good link for this and didn't spend a long time searching, but this seems okay
[8]: https://stackoverflow.com/questions/681953
[9]: https://eev.ee/blog/2016/11/23/a-rebuttal-for-python-3/
This describes my C++ experience precisely. Initially I was a C# programmer learning C for embedded projects and it went exactly like this.
C++ has a lot of really neat features that sure look powerful if your application aligns precisely. It seems like every time I try a new feature, what I want to do is always an edge case that doesn't work in my situation. I try for a few hours/days before I give up and just write it in C.
My biggest disappointment with C++ is that the standard libraries are completely unsuitable for use in embedded systems where you have to control when, where and how many memory allocations occur. This is particularly important when the system level design choice is to perform all allocations at system startup, which is a common design pattern for high performance systems with real time characteristics. A high speed messaging system I worked on ran into this all the time. We couldn't use the standard implementations of things like heaps, hashes or queues because they don't have a way of making memory allocations occur at startup. It was quite common to have to re-implement those data structures when adding a new feature, as the stl or boost implementations were not suitable for this design pattern.
Have you tried override the allocators and destructors of your classes so you can do all allocations at startup?
chatgpt or copilot helps a lot with that. I usually copy the text and let it decipher the thing for me.
they’re still bad for generating any significant body of code.
They’re a little bit better about deciphering errors.
They’ll still bullshit* you and send you on wild goose chases.
*hallucinate if you prefer
> They’ll still bullshit you and send you on wild goose chases
And confidently at that. It can't seem to find the backbone to say no to me either.
If I say something like "wait, X doesn't seem to make sense, isn't it actually Y and Z?" it will agree and reformulate the answer as if Y and Z were correct just to placate me. I usually use the LLM to learn new things, I don't don't actually know if Y and Z apply.
I’m glad someone is having fun in C++. Personally, after >20 years, I have to be done with C++. It’s just a mess.
If I really need the low-level control, I can wrangle C (warts and all), otherwise Rust, Python, etc just make me happier.
Agree. C++ is here to stay though. Too much code written in that language. It's certainly a great skill to have, and there are super lucrative jobs too.
For me, I feel the language just go in the way and is way too complex. Sure, I know the discourse: "modern C++" is great, you don't need to learn about older. versions of C++, things are getting simpler with each new version.
The problem is that the codebase you get to work on aren't modern C++. They use every possible feature ever released and it's just hard and full of pitfalls.
I suppose people who have only work on C++ in their projects for years can develop some level of expertise and are immune to the complexity. But really, not a great language...
One of the things that has made C++ more tolerable over time is that it became easy to seamlessly replace most of it with an implementation that works the way you think it should and with most behaviors being fully defined -- the core data types, standard library, etc. Particularly with C++20 and later, alternative foundations feel quite native and expressive.
Most new C++ projects I see lean into this quite heavily. The ease with which you can build a systems-level DSL for your application is a strength of C++.
There's a parallel here with modern applications perl as compared to the crawling horrors people perpetrated in CGI scripts during the dot com boom.
Also present day javascript (the language; the ecosystem is another matter ;) compared to the 'var' and IIFEs-for-scoping era.
I take a few features from C++ in my C++/SDL2 Ultima spinoff project -- never completed sadly. Class, std::function, std::unordered_map, std::string, std::unique_ptr<type> are the only ones that I could recall.
I can't imagine reading other people's code though, unless it's in a similar style. I did find that the most difficult part is to get past the programming patterns (such as Visitor pattern) as I don't write large programs professionally.
I wish C++ stopped at C++/11. The committee seems to want to include everything into it at the moment. Maybe C++ is sort of ULTRA programming language that supports every niche programming style.
FWIW, C++20 is a much tidier and more usable language than C++11. The difference between C++11 and C++20 is almost as large as C++11 and legacy C++.
What is your Ultima spinoff project?
Ah that was my pet project to create an engine that runs a game similar to Ultima 1-3. I even used the Ultima 4 sprite sheet. Never completed it though.
C++ is an absolute mess.
But it's a fun mess, and I like writing in it :) Sometimes, that's important.
My relationship with, say, Rust is much colder: I respect Rust, I use Rust where I think it makes sense (especially in e.g professional contexts where I often can't defend the use of C++), and I think what it's doing is important. But I find it tedious to write, despite having a fair amount of Rust projects under my belt at this point. It's better in key ways, but not as enjoyable, for me.
Rust is fun for me but I keep it high level with Rc and built-in data structures. It's fun thinking about variables as little boxes of fixed size that values get moved into or out of. It's so different than almost any other language where stuff lies often god knows where and is referenced ad nauseam. Although that can be fun too if the language treats mutability as exception rather than a rule.
> I'm glad someone is having fun in C++. Personally, after >20 years, I have to be done with C++. It’s just a mess.
I've spent a couple of decades working with C++, and the truth of the matter is that, as much as it pains the bandwagony types, C++ became great to work with once C++11 was rolled out. The problem is that teams need to port their projects to >C++11 and that takes an awful lot of work (replace raw pointers with smart ones, rearchitect projects to shed away C-style constructs, etc) which some product managers are not eager to take.
I firmly believe that the bulk of this irrational hatred towards C++ comes from naive developers who try to rationalize away their lack of experience by blaming everything on the tool. On top of this, cargo-cult mentality also plays a role.
What makes this quite obvious is the fact that the solution presented to all problems always comes in the form of major rewrites, even with experimental and unproven features and technologies. Writing everything again is always the solution to everything. Everyone before them knows nothing whereas these messiah, cursed with being the only ones who see the one true answer, always have a quick and easy answer to everything which always boils down to rewriting everything from scratch with the flavor of the month. Always.
Weird, huh?
> C++ became great to work with once C++11 was rolled out.
Have Yossi Kreinin's objections (https://yosefk.com/c++fqa/defective.html) been addressed yet? In particular, can I reuse source code from another file without a text preprocessor yet? Can I change a class' private members without recompilation, or am I still stuck with indirecting that behind a "pImpl" pointer in the class declaration in the header file? (Being able to use a smart pointer for that is not addressing the problem.) Are compiler error messages resulting from type mismatches reasonably short (without third-party tools like STLFilt) yet (never mind whether they're informative or pleasant to decipher)?
I know that "some parts of the FQA are not up to date with C++11 [and onward]", but I haven't heard of these problems being fixed.
> Have Yossi Kreinin's objections (https://yosefk.com/c++fqa/defective.html) been addressed yet?
A cursory read of that list is enough to see it's a list of complaints fueled by a mix of ignorance and disingenuity.
For example, the first entry complaining about "no compile time encapsulation" is somehow completely ignorant and oblivious to very basic things such as the pimpl idiom. I mean, this thing is notorious in the way it allows Qt to remain binary compatible across even major version bumps. And yet, "This makes C++ interfaces very unstable"?
The list reads like pure nonsense, to be quite honest. At some point the author gripes over the lack of garbage collection. Which language lawyers know very well that until somewhat recently C++ standards actually had provisions to explicitly support it, but whose support was removed because no one bothered with it.
Is this your best reference?
Yossi is aware of the pImpl idiom and refers to it explicitly in section 16.21 of the FQA. It adds the overhead of indirection; in particular, it means that even when you don't use polymorphism and were able to avoid the cost of a vtable, you still don't get to have an array of instance data contiguously in memory. And it's still something you have to do manually; you don't get it by default. It seems clear to me that this simply doesn't meet Yossi's standard for "compile time encapsulation".
>At some point the author gripes over the lack of garbage collection. Which language lawyers know very well that until somewhat recently C++ standards actually had provisions to explicitly support it, but whose support was removed because no one bothered with it.
Other people not caring about garbage collection doesn't mean it's a missing feature. It's clear why operator overloading in particular would benefit from the ability to make temporaries without worrying about the memory they allocate. (Of course, this was written in an era with a much poorer ecosystem of "smart pointers".)
>Is this your best reference?
It's not as good of a reference as I remember it being, I suppose. It has been a long time. But what I've seen of C++ in the interim, bit by bit, has generally made me less inclined to pick it up again, not more. The complexity just doesn't seem justified.
> C++ became great to work with once C++11 was rolled out. The problem is that teams need to port their projects to >C++11
The problem is the C++ that's not great to work with is still there, and there's nothing preventing the rest of the world from using it; there are always going to be naive developers with a lack of experience who don't know how to use the tool. For this reason, all the code that's possible to write in C++ will be written, that includes the unsafe code.
It's not enough to have a safe, nice, modern, subset of C++ that everyone "should" use. If developers have the option to use the warty, sharp, foot-gun infested version of C++ they will, and they will gift that code to the rest of us in the form of readily exploitable software.
This is why organizations like CISA are suggesting developers move to other languages that take a stricter posture on memory safety: https://www.cisa.gov/news-events/news/urgent-need-memory-saf...
> companies should investigate memory safe programming languages. Most modern programming languages other than C/C++ are already memory safe. Memory safe programming languages manage the computer’s memory so the programmer cannot introduce memory safety vulnerabilities. Compared to other available mitigations that require constant upkeep – either in the form of developing new defenses, sifting through vulnerability scans, or human labor – no work has to be done once code is written in a memory safe programming language to keep it memory safe.
> The problem is the C++ that's not great to work with is still there, and there's nothing preventing the rest of the world from using it;
That's precisely why all this criticism is actually thinly veiled naive inexperient developers blaming the tools. Selling full rewrites as solutions to the problems they created is a telltale sign. As they are lacking the experience and know-how to fix the mess, they succumb to the junior dev disease of believing deleting everything and starting from scratch is a solution to all of life's problems and inconveniences.
> naive inexperient developers blaming the tools
That's not the problem. It's naive inexperienced developers using the tools. Most developers have to maintain code they didn't write themselves. One can learn all the C++ best practices in the world, but it won't protect you from other people. That's why languages with strong restrictions and constraints that force safety and correctness are needed. With such languages, naive inexperienced developers won't be able get anything to compile. We won't have to deal with their mistakes as they'll never be able to ship them. Any experienced developer would surely want this.
A rewrite is not pointless if you are rewriting into a language with additional guarantees. You are checking for and proving the absence of certain classes of software flaws by doing so.
> With such languages, naive inexperienced developers won't be able get anything to compile.
Hey dude, I can’t get this thing to compile?
Just wrap all your variables in Arc; that’s what I always do.
I don’t see how juniors, who want to rewrite things, are the reason why C++ has 3 freaking ways to initialize a variable.
There’s a few more than three.
https://leanpub.com/cppinitbook
https://www.reddit.com/r/ProgrammerHumor/comments/8nn4fw/for...
Is Rust not meant to offer the same kind of control and "low-level-ness" as C? Like, can't you cast integers to pointer explicitly if you break the `unsafe` seal (and thereby e.g. do memory-mapped IO to control hardware)?
Same. I wish someone made a better C++ from a blank slate. And no it's not Rust.
Beware of C23 and later.
C is basically going into "we want C++ but without OOP and with templates done via _Generic".
Also LLVM and GCC aren't going to be rewritten from C++ into Rust anytime soon.
> Beware of C23 and later.
Beware of what? C23 fixed a number of issues. Sure, there are some oddballs (QChar and auto, mostly), but overall I think C23 is an improvement.
Those C89 hardliners have a different point of view.
Also there is the whole point Objective-C and C++ are much better than those improvements will ever be.
> Those C89 hardliners have a different point of view.
C89 was only a thing because Microsoft somehow decided to drag it's feet and prevented msvc from supporting anything beyond c89 for a long, long time. Only in 2020 did they manage to finally do something about it.
https://devblogs.microsoft.com/cppblog/c11-and-c17-standard-...
Because for Microsoft C belonged into the past, it was about time to move into C++.
https://herbsutter.com/2012/05/03/reader-qa-what-about-vc-an...
They only changed of point of view after Satya, and the whole Microsoft <3 Linux and Microsoft <3 FOSS pivot.
And in any case, blaming Microsoft doesn't really work out, as many of those folks don't even care Windows exists, only UNIX/POSIX platforms.
> Also LLVM and GCC aren't going to be rewritten from C++ into Rust anytime soon.
I'm afraid you underestimate the will power of rustaceans to find literally anything to rewrite.
I doubt Cranelift will ever match the only project that has beaten the contribution level of Linux kernel.
I imagine most C programmers are still using C99 or older anyway, particularly in the embedded space.
I'm not sure this is still true. I'm just a hobbyist but esp-idf, for example, supports C++23.
esp-idf is still on GCC 11, but most of the features of C23 are in GCC 13.
C99 or newer. You gotta have standard fixed size types like int16_t.
Not in the embedded space. When you've worked on embedded systems long enough, you learn that you have to accept the compiler that the vendor provided you with, and you adapt your codebase to the limitations of that compiler. This means working around code generation bugs, adding #ifdefs to define typedefs for things like int16_t if they don't exist.
That said, things are a lot better than they were 15 years ago, and the mainstream ARM compilers used today are leagues better than the barely functional cobbled together ports from the early '00s. ARM64 is a tier 1 platform, and there are enough users of the platform that the extended QA team that embedded developers were unintentionally part of in the past is no longer really the case (at least when it comes to the compiler).
However, there are still truly obscure architectures (mostly 8 bit and 16 bit) that continue to have oddball compilers. And you accept and deal with that insanity because it means you're only paying $0.01 for that microcontroller to blink an LED when some kind of an event occurs.
What part of C23 makes you believe this?
Everything that was taken from C++, and _Generic.
And the ongoing discussion for lambdas that didn't make it into C23, but still on the table for C2y.
Meanwhile, zero progress in what actually matters, proper strings and arrays, that aren't a continuous source of memory corruption bugs.
This is a long rant that covers a lot of ground, a lot of which will inevitably be ignored because the letters "C++" trigger people, myself included sometimes. (Skip ahead to "It's Not All Puppies and Butterflies" for some of the complaints.) The author is really impressed by C++11, as am I, after purposely ignoring C++ for the better part of twenty years.
I appreciate the shout outs to some packages and libraries to play with, although I still often find it a pain to incorporate other libraries into my projects. (Single-file headers, anyone?) I'm intrigued by FTXUI.
And boy, howdy, he's right: cppreference.com is amazing. Python's documentation is pretty good, but I've never seen anything as good as cppreference.com.
cppreference.com is very, very good at being reference, as in the name.
Python's documentation is scattershot and incomplete in many places, and lacks a consistent copy-editing style - but it offers good coverage of all kinds of documentation (per the Diataxis framework), not just reference. The people writing that documentation explicitly take that framework into consideration and use it to look for ways to improve. (But it's still a volunteer effort that works basically the same way the code development does, following open-source principles, so.)
The article filled me on a lot of things I didn't know about C++ because I learnt it at school and college, but soon moved to Python/JavaScript for day job. I have been itching to "get closer to the system" for a while now, and learning Rust on the side hasn't been easy. This article gave me hope that, I might be able to do that if I refreshed C++. Hello CMake… or I should probably say, Meson.
If you're starting a new project with a talented team that knows how to use modern C++ well, I agree that C++ is great! It's a pleasant and powerful language, delivers great performance and (while complicated) is straightforward to debug and optimize.
I had the privilege of working on a codebase that was about 5 years old, written to C++11 standards or later, and I thoroughly enjoyed it, for many of the reasons expressed in the article.
If you are working on an older codebase that has evolved over 20-30 years, or one that has not been maintained by talented people, you will have a very different experience.
I started my life as a dev with PHP, learning it on the job to set up a custom Wordpress theme with plugins for special datatypes (eg STCs).
When I was 21, they offered to pay for my tuition so I went back for a CoE degree (later switching to CS). The moment I touched C++ - on my very first day in their “intro to programming” class - I fell in love. The type system, debugging, and ecosystem was fantastic compared to PHP 7 that I was using at the time. With my manager's permission, I went back to some of our existing intranet apps and built a C++-based API for them that our PHP would call over a web-socket, so that I could leverage the type system (and performance). Those systems are still in daily use, company wide. Maintenance hasn't been a problem. And that’s despite running in a really weird environment (IBM Pase for i).
Now, after using Rust and Axum, I'd vastly prefer the Rust ecosystem for creating that type of API. But C++ deserves credit. It really is a great language, especially if you’re coming from web languages like (non-TS) JavaScript or PHP.
> If you are working on an older codebase that has evolved over 20-30 years, or one that has not been maintained by talented people, you will have a very different experience.
I'm trying to think of any other language that could fit that description and not also be a terrible experience. In fact, I'd prefer C++ to many other possibilities I can think of (C, Java, Fortran). The exception would be C#, but I'm not sure if it's old enough to fit.
Fair point, although I think it's true that C++ gives you more rope to hang yourself with, and if I had to choose, I'd rather maintain a crusty old codebase written in Java than one written in C++.
The issue is that the JVM has started deprecating features which old codebase rely on.
I don't work with it, but even I know about the JVM no longer supporting sandboxing or being able to kill threads programmatically.
C++ doesn't have this issue because you can still deploy code from an old toolchain onto a modern OS.
You can and always have been able to .destroy() threads in java, but doing so breaks the memory model in ways that make it clear that you should never do it. My guess is that if you're kill heavy in c++, then you've also run into bugs waiting on locks of threads you've now just murdered. That's why there's a ton of great abstractions for how to "kill work running somewhere else" that doesn't leave your application in a potentially unstable state.
Java 9+ sure did kill off a lot of things that weren't particularly ideal. Most normal devs who don't write libraries probably can't even name a single feature removed (corba, script interpreters, some jmx cruft, etc). Most of what was culled still exists in libraries that could easily be added back with project imports. Maybe Java applets are super dead now? I don't believe any modern browsers still support native plugins, but maybe there are some niche individuals bemoaning the loss of java web start.
As for the "can still deploy code" comment, the same applies to java. Look around, and sadly you see very old releases of java still in use today. My guess is that if I fired up a java 1.0 compiler and JVM today, it would run on my PC (poorly).
Before criticising something, please consider being well enough informed to warrant the comment.
So that's a fun one. C# the language people know first released in y2k and the 1.0 release was 2002.
So it definitely is old enough to fit and it certainly has it's warts from age that show up in long lived projects.
I used to work on a codebase at Microsoft that was classified as a microservice. Pretty much entirely written in C# and it was about 12 years old.
> C++ has built-in regex now and they're pretty damn good too
Sadly, `std::regex` is anything but good (poor performance, lack of Unicode support) and should be generally avoided.
The linter used at my work insists on not using it....
Is there something wrong with the specification that causes poor performance?
The underlying issue is that for the matches the interface relies a lot on heap allocations for the individual matches, leading to a lot of allocations of small regions to copy from the original input. Many other libraries provide a lot more control there.
In benchmarks std::regexp often is a lot slower compared to other implementations, independent from the standard library implementation of choice.
The big upside compared to all others is that it's always available. But if there is a choice alternatives are often better.
It’s ultimately a “won’t break ABI” issue: https://stackoverflow.com/a/70587711
I learned C++ and enjoyed it but never went too deep. I always enjoyed C more. There's also so many S tier codebases to read to learn C better like the original Dune game engine or Unix utilities.
Having dabbled a bit with Rust recently I can't see any strong reasons to use C++. The combination of strong functional programing inspired type system with performance seems unbeatable. Almost a perfect language.
I'm sure there must be some legacy reasons to use C++ though. Maybe Game Engines, embedded programing, some kind of other legacy tie in?
Huge amount of legacy across many dimensions, not just apps written in it [1], like number of users, published and available knowledge / resources (books, courses, blogs, articles, videos, software libraries, etc.), high compatibility with another huge language (C), etc.
This is just software industry general knowledge, for those who have been there for more than a few years in the field. I am not even a proper beginner in it, because I have never used it much, although I had bought, read and to some extent, understood some classic C++ books, including by the language creator (Bjarne Stroustrup [2]), Scott Meyers [3], and a few others, earlier. I did have a lot of experience using C for many years in production, though, including on a successful commercial product.
[1] https://www.stroustrup.com/applications.html
[2]:
https://www.stroustrup.com
https://en.m.wikipedia.org/wiki/Bjarne_Stroustrup
[3] https://en.m.wikipedia.org/wiki/Scott_Meyers
HFT perhaps?
I can see that.
On most metrics I've seen Rust is comparable on general speed.
Maybe if you're at the level where you've essentially writing portable assembly and are okay with lack of safety. You need to know exactly what is happening within the CPU, maybe on custom hardware.
I bet some defense applications would be in this category too, although for my own sense of self preservation I would prefer the Rust type system.
Rust would probably be a good fit for HFT, but as the field is so dominated by C++ is hard for another language to make inroads. Java managed to some extent.
I would expect a lot of unsafe though.
The fundamental problem with C++ is that it has hiding ("abstraction") without safety. That's rare.
- C -- No hiding, no safety
- Python, Javascript, LISP, other interpreted languages -- hiding with safety
- Pascal, Ada, Modula, Rust -- hiding with safety
- C++ -- hiding without safety.
C++ is, decades late, trying to get to hiding with safety. But there's too much legacy.
Well C has functions. Isn't that the original hiding abstraction?
It is an abstraction, but the safety requirements are neither enforced nor abstracted away.
C's type system can't communicate how long pointers are valid for, when and where memory gets freed, when the data may be uninitialized, what are the thread-safety requirements, etc. The programmer needs to know these things, and manually ensure the correct usage.
Using c++ after learning rust feels like picking through moldy bread
Using C++ after Rust made me appreciate the latter a lot more. You quickly learn all the footguns that the compiler stops you from doing and is generally a good learning experience.
Plus the Rust compiler actually gives you helpful error messages. C++ compiler errors might as well be in Klingon.
This is what I feel as well. I liken it to using an aimbot in Quake. Turn off the aimbot and you still win because the aimbot trained you how to get headshots. There are many times the Rust compiler told me I couldn't do something I had insisted would be fine, only to ponder and realize in fact what I was doing would cause subtle bugs in an edge case. Rust catches it at compile time, C++ allows you to write the code and sends you a segfault when the subtle edge case occurs in production.
Segfaults in production are the good case. They're when the system recognizes you've made a mistake and stops it from propagating. The bad cases are when the program keeps running but silently does the wrong things.
Yes! This can be a real problem when your data structures are allocated from a memory pool. Since the whole memory region is owned by the program, out-of-bounds writes will either do nothing or silently corrupt your program state.
The good case is that you catch the error in development and it doesn't even get to production.
Strings!
Strings in Rust aren't that great, because there are some details you must keep track. But when I was first learning it, I eventually had some desire to rewrite stuff in C++, and always stopped at the first thought of dealing with strings.
I worked with c++ for many years and it has lots of warts. Slow compilation, lack of decent build system (no cmake isn't that great), mistakes made in the standard library (iostreams), weak cross platform standard libraries were always a headache. But I always loved the power that <algorithm> and the like provides, although attaching allocators (and other template things) kind of sucks.
When I looked at and test drove rust all I saw was heaps of complexity stacked up yet again. Probably didn't help that I smacked headlong into the Arc Mutex and lifetimes mess. I'm really more inclined to go for something like 'zig' which does so much with simple syntax (and awesome comptime) and still gets excellent performance.
> no cmake isn't that great
Nor is cmake even really a standard: after thirty-odd years writing C++, I've yet to work on a project which used it!
That's pretty damn impressive! How did you manage to avoid codebases with CMake? Or are you just saying you personally chose not to use it for your own?
> lack of decent build system
At one point I was using gulp.js to build my small C++ experiment.
A well written blog post that makes many valid points but comes with a wrong assumption: that making a language creativity-friendly is an absolutely desirable trait.
It's a desirable trait for my personal projects, where I may use Haskell, Ruby, Ocaml, Racket or something more exotic.
At work, I'd rather use languages that are boring, with well defined and uncreative patterns and practices. Professionally I expect to not be surprised often and I want the smallest group cognitive load possible.
Languages that breed too much creativity tend to have a rather short list of killer software (that kind of software that makes it worth learn a specific programming language).
I wonder if Zed started using, or went back to, Perl after getting so worked up about the 2 -> 3 transition. (From what I can tell, he's still complaining about it, and still wrong in many of those complaints, and makes unsubstantiated claims in others.)
I feel that way with Ruby vs Python sometimes.
> I want you to ask yourself an honest question. When was the last time you actually had fun in programming?
Every time I write C code.
For me, C is fun until I hit a certain level of abstraction complexity involving fake homespun vtables and it starts getting harder than it should be to chase down bugs.
Everytime I need to write C, I wish I could use C++ instead. The things I miss most are RAII, templates, lambdas, const-correctness, and most importantly, a standard library with container types and algorithms.
What is wrong with const-correctness in C?
I think container types and algorithms is a fair point, but if you program C more you should have a go-to library or your own implementation.
I agree, I switched from C++ to C and I found it relaxing to be able to just forgot about a million language features and their complicated interactions.
I also find you have some experience, know how to build good abstractions and have a set of good data structures, there is no issue with address complex problems in C.
every time I write C code, I don't want to remember how to implement unordered map
You mean
Apologies for the telegraphic variable names and weird control flow. I wrote this on my cellphone. Lacking these 15 lines are what keep you from writing C?There's a nice tutorial on hash tables in K&R, and I can also recommend learning about Chris Wellons's "MSI" hash table design, described in C at https://nullprogram.com/blog/2022/08/08/. He shows a more full featured hash table in about 30 lines of code, depending on what functionality you need. It's eminently practical, and usually a lot faster than generic algorithms.
That's not an exceptionally simple hash table either. One night I hurriedly wrote a rather long-winded implementation also on my cellphone (strings, with separate chaining and a djb-style hash function) and it also came to about 30 lines of code: http://canonical.org/~kragen/sw/dev3/justhash.c
You can't use anyone else code and always delete your own previous implementation?
I'm gonna be weird and not engage in language war (I love c++)
but, dear author:
> +95% of the compiler errors
this. this is unforgivable
+X means "additional X". As in "I have Y and I add +X to it". When you are invited to an event, your invite states you can take your +1 with you (one more person that will come within the same invitation)
if you want to say "more than", you use X+ !! as in "95%+". Because you have some X amount, and you add some more to get X+...
get it together. you're awesome
Come write products on top of Unreal Engine, you will have the opportunity to dive into 20+ MLoC of real time C++ goodness. Make sure it's a multiplayer experience for bonus points, eventual consistency makes everything extra exciting.
It gives you an appreciation of just how unlikely we're to ever move away from the stuff, short of an LLM innovation that can digest codebases of that size and do an automated port, which I suppose is not outside of the realm of reality these days.
There's too much wrong in this rant to list...
I think we can start with "C++ is not popular enough to attract the weirdos".
Presumes C++ is not popular and also popularity attracts weirdos. If anything, weirdos are attracted to languages that are not popular at all. I remember once I was on a small language project and this guy on the mailing list wouldn't stop going on about how our language had to support vorpal math.
> Well, actually the Rust weirdos will bother you but they're usually too busy making the borrow checker happy to actually get anything done so you can ignore them.
Ouch...
While it is a great language, ir would profit from less "lets code C with C++ compiler" attitude.
Basically it is like renaming those JavaScript files from .js to .ts, and keep coding as if nothing else is available as productivity and safety improvements.
I feel like an abused spouse after C++. I now avoid:
- Inheritance - Reference counting - Threading - Templates - Classes if possible - Hidden memory allocation - Anything that looks clever
Anytime I use them I get flashbacks to some mangled mess of templated threaded classes with some memory leak that shows up after 3 days.
I remember writing C++ and trying to figure out how the design would work between these classes, I would end up with something complicated and not entirely correct. Eventually, I thought, what if I did this in C? What would it look like, 90% it turns out with 90% less design and code (and bugs).
There is no rule that requires "programming in C++ proper" being equal with using 100% of the language standard.
There is a middle layer, without having to keep repeating all the security flaws of coding in plain C.
You don't have to use any of that and you still get lots of nice things like range based for loops, STL containers, algorithms, namespaces, and constexpr.
Having read the article, nothing really stands out to me as "C with C++ compiler".
It talks about ranges, shared/unique pointers, lambdas... Essentially a lot of things C is lacking. I don't know where exacly the overlap you're insinuating comes from.
Where did I mentioned the article?
Pronoun confusion. The second pronoun is ambiguous.
Since we are all “hackers” here, I’ll be pedantic…
“While it is a great language…”
The “it” pronoun clearly refers to the C++ language, as I’m sure you intended.
“…ir would profit from less ‘lets code C with C++ compiler ’ attitude.”
The “ir” — presumably a typo for “it” — can refer to the article or C++. Given that this thread is about an article, the second “it” referring to the article is a natural assumption.
It with typo refers to C++.
OK, so I admit I also washed my hands of C++ sometime around 2009 and I am being forced back into for <reasons>, and I had no idea what these auto and lambda keywords were.
Can anyone point me to a learning reference that will let me jump the meta programming apocalypse and just get to the good stuff?
The "Back to Basics" videos from cppcon are pretty good, IMO.
For lambdas: Back to Basics: Lambdas - Nicolai Josuttis - CppCon 2021
https://www.youtube.com/watch?v=IgNUBw3vcO4
Back To Basics: Lambda Expressions - Barbara Geller & Ansel Sermersheim - CppCon 2020
https://www.youtube.com/watch?v=ZIPNFcw6V9o
For auto, this one is short and summarizes some of the gotachs:
C++ Weekly - Ep 287 - Understanding `auto`
https://www.youtube.com/watch?v=tn69TCMdYbQ
jump into learncpp and choose where to start from, goodluck
I've had a similar situation with Kotlin. I've always been a java developer, and I enjoy using it, but even with the newer features it's just...slow.
When I had to script things I chose JavaScript (native JavaScript) since it's way faster to iterate, but I've always missed the static typing (I also know python, but I honestly prefer JavaScript)
Until I learned Kotlin. It's been a blast to use, incredible common libraries, streams everywhere, nulls that you can use without issues...I just love it (so much in fact that I'm in the process of switching project from java to Kotlin).
When I need to do scripts, like for the advent-of-code, I choose Kotlin.
I also really enjoyed the jump into Kotlin as well! That said, I'm also very happy with recent java versions trying very hard to close the gap. It does feel like at least half of Java's recent major lifts have been to emulate things done well in other languages (a great attribute for a self-aware language developer). I definitely never appreciated a lot of their newer feature choises like records until I saw them working really well in Kotlin data classes.
For me, it's basically C++ for low-level, Python for high-level, Java for insanely large corporate codebases, JavaScript (oh boy...) for the Web. Tons of better languages are out there, but they lack the same level of support, tooling and libraries' availability.
Every article like this I scan to see if the author had previous C++ experience. And every article, they do.
I will be very impressed and curious if I find a glowing article about C++ from someone who didn’t grow up knowing it as a smaller, simpler language.
The C++ community needs enthusiastic converts who didn’t do it back in the 2000s if it’s going to stay relevant.
I learned c++ after c++20 and after several attempts to enjoy rust, c#, go, and C, I always come back to c++ as the most enjoyable language to develop.
Nice! If you blog at all your perspective would be super interesting to hear.
> Need package management? Check out Conan, Meson's WrapDB, and vcpkg.
No, just no. All of them are complete, utter crap that doesn’t hold a candle to languages that were designed with packages in mind. We’re using Conan at work, I’ve been using vcpkg at home and I loathe both.
> I mean, do you really think Python's package management is top notch? You do? Why are there like 10 package managers then?
Worst Python package manager runs circles around whatever C++ offers.
I don't think we need to pretend Python package management is good to contrast C++, they're both awful for different reasons.
At least there's maybe a way out for Python (uv / the various PIPs); C++ doesn't appear to have any kind of plan whatsoever, outside of gesturing in the direction of modules.
what's wrong with vcpkg? looks good to me
> I remember insane discussions with people who thought adding two numbers with a template was "faster" than adding two numbers directly, even though the assembly language output was exactly the same.
I know almost nothing about C++ and have never programmed in it... but I thought that the point of that kind of 'template metaprogramming' was that the code got executed at compile time instead of runtime?
i.e. instead of generating identical assembly output the goal would have been to output a constant value
As per usual with c++ it depends. In some situations things are guaranteed to be compile time evaluated. E.g. even in early templates this would need to work:
Func<1+2>();
Something like this there's no guarantees around:
template <A, B> int Func() { return A + B; }
For almost all compilers it should constant fold this into a constant but in theory it could end up with an add instruction. Basically we can't second guess the author here because it depends on the specifics.
For the love of God when will c++ compilers finally be able to output template errors that aren't completely expanded and are written in terms of the user's typedefs? Most of the time I spend parsing template errors with boost is just to figure out what the hell is being complained about.
Weren’t concepts supposed to fix this? Apparently they made it into the 2020 standard. I haven’t touched the language in many years - did they not help?
Concepts did not actually make it into the standard. I vaguely recall they were cut at the last minute or something.
Concepts are in C++20. I don't know the specifics but it's my understanding that the version we got is stripped down in comparison to the original proposal.
Oh, woops. Maybe they I only updated my belief before 2020. OP is right about one thing, cppreference is great. https://en.cppreference.com/w/cpp/language/constraints
I have found LLMs are a great tool for metaprogramming. I think the template error problem has been wanting for a sufficiently advanced compiler, and that's what I see LLMs as being. ChatGPT has been a great help in debugging programs I've written in C++ templates, both in generating the template code and trying to decipher errors generated, leading to suggestions for the template code rather than the expanded syntax.
Yeah, totally. I find LLMs are very useful for doing stuff with the preprocessor, too. ChatGPT taught me how to use boost preprocessor (BOOST_PP_FOR_EACH_PRODUCT).
Still though, I want to see MyMapType::value_type in compiler errors rather than... Well, you know. It's going to contain the type of the key, the type of the value, the type of the allocator, just when all you want to really know is that it's a pair<key, value>, which I think most people know of as My map type::value_type.
There were 91 occurrences of the word "rust" in 264 comments so far.
Only 7 instances of “footgun” (or a variation) though. HN is slacking - must be the holidays
I've had some good times in C++. But for everything that's been thrown into it, I can't believe we're still dealing with header files. That was one of the greatest things about moving to Swift: no more of that BS.
But with SwiftUI, Swift has also become "unfun." SwiftUI and Apple's half-assed, broken observation and "reactive" paradigm have made programming a joyless slog.
I did C and C++, then moved on to Objective-C and Swift. I recently switched back to C++, after getting tired of Apple’s shit treatment of developers. I also have no interest in learning SwiftUI.
Having to define header files in C++ is pretty annoying after doing Swift for many years.
> I've had some good times in C++. But for everything that's been thrown into it, I can't believe we're still dealing with header files.
There is nothing wrong with header files. In fact, there is no such thing as a header file specified in C++. There are only forward declarations and definitions, and how those can be managed by developers.
Meaning, any talk about header files is a discussion on software engineering practices, and how developers create or avoid their own problems.
"There are only forward declarations and definitions, and how those can be managed by developers."
Why do we need to manage them?
Because nobody has figured out a way to automate the process of generating declarations from definitions.
> Why do we need to manage them?
Why do developers need to write code that makes sense and does what they want it to do?
Why do developers in other languages not have to deal with it?
> Why do developers in other languages not have to deal with it?
They do, except they don't have the bandwagon effect motivating them to complain about other solved problems.
Enjoy your echo chamber.
> Enjoy your echo chamber.
Is that supposed to mean anything at all?
C++20 has modules, which replace header files completely (unless you use old libraries which aren't available as modules yet). Compiler support is there, but unfortunately IDEs are lagging. If you use modules with Visual Studio, say goodbye to IntelliSense. Maybe they'll iron out the bugs in a couple years...
C++20 that is not complete in 2025. I don’t know how C++ developers can work in this with a straight face.
You probably have to think this way: Anything added to C++ will only get to be used in for real for real in 10 years minimum.
Wow, that's ridiculous.
I wonder if Xcode does any better with them. Now that would be something.
They needed the most powerful, most flexible module system ever, so it might take decades to really become useable. Adoption has been painfully slow so far, it's insane complexity really doesn't help.
C++ has modules for small values of "modules": https://arewemodulesyet.org
It's a bit tongue in cheek but: "Are we modules yet? Nope. ... Estimated finish by: Wed Sep 20 2541"
You can use modules to structure your own codebase. No more need to write headers and think about how to structure your code in terms of compilation units. But yeah, your link shows that practically none of the popular libraries (except STL) can be imported as modules today.
I plead the 10th
https://en.m.wikipedia.org/wiki/Greenspun%27s_tenth_rule
I have the luxury of only writing software for myself, which is almost always Python, but after reading this ill go back and have another look at C++. Did one big project in C++ after uni but then never touched it again.
It takes some getting used to. I wrote it for about 10 years then didn't touch it for 10 years and now I write it full time again. Took a few months to get up speed again and stop shooting myself in the foot.
A bold statement from Ken Thompson on C++: https://m.youtube.com/watch?v=c-P5R0aMylM
> I firmly believe that for creativity to blossom you need to be able to get your idea out without fear of criticism and shame.
This section is, it seems to me, the linchpin of the "fun" argument. But you've been able to do that with all the other languages too, all this time. The much-loathed and feared Rust Evangelism Strikeforce doesn't actually come to your house and make you use a bunch of generic-heavy code from crates.io. The React people can't stop you from using vanilla Js. The worst they can really do is send you mean tweets, but Shaw thinks this is a lethal threat to his creativity, enough to switch language ecosystems over. For an article written in a superficially rebellious, lone-wolf tone, that's kinda sad.
>I really hate RAII.
I'm baffled by this. Deterministic destruction (necessary and basically sufficient for RAII) was one of the best design decisions in C++, I think. RAII means you can't forget to clean up a resource. You don't have to type anything for it to get cleaned up at the right time, it just happens. It's basically what GC promised developers, except for all resource types, not just memory. Your code gets shorter and more correct.
I'm extremely curious to hear how this could ever be bad.
I really love how politically incorrect this post is. And how honest and fresh it sounds, though quite a few have been quick to say how wrong he is. They could be right, but I think it misses the point.
Yes, it's way too easy to do dumb stuff in C++, when you're tired or not sure what you're doing. Things like holding raw pointers or references to things you shouldn't like std::vector::data(), or questionable reinterpret casts and many other things. The compiler won't stop you, only your experience.
But he's right about one thing at least: Programming should be fun!
All these layers and rules and concerns about memory safety and security don't offer only advantages. They also have tradeoffs. And it's the same thing with those scrum agile ceremonies. It serves its purposes. But it's also the best invention ever to suck all the joy out of programming.
I think that both C and C++ still have that fun feeling going for them. When you know what you want to work on and how to do it and you just start doing it and get into the flow. And if you're careful and do things right, it just works and it's a blast!
That's my takeaway from the article. That feeling like you're talking directly to the machine, getting it to show you on screen what you saw in your mind, without anything else getting in your way. Now, that is fun!
Programming should be fun. I don't think that's controversial or politically incorrect. (Or if it is, I don't know why.)
A lot of people don't naturally have the kind of fun in C++ that Zed describes, and it seems most people here (including myself) would rather talk about that.
It's controversial because of how he makes fun about the people using and promoting other languages, like Rust. I thought it was hilarious.
And in case you haven't noticed, C++ isn't seen in a good light anymore for some years now. There are a lot of loud voices saying its time has past and calling for it to be replaced with something newer and better.
It's all subjective anyway, but I resonate with the feeling of fun he describes when doing projects in C++. Feeling productive and being protected from whole classes of bugs common in C++ is all well and good, but he was talking about programming being fun and I do not get that same feeling when programming in other languages.
It's perfectly understandable that you don't feel the same way though. I hope you do when programming in your favourite language. Otherwise it becomes just something you do to pay the rent.
Zed is definitely the sort of person who can make an uncontroversial point sound controversial, yeah. I think we're in violent agreement here.
"I really hate RAII" but smart pointers are great?
Can anyone recommend some good “modern” C++ books in this spirit (writing software for fun, not large enterprises)?
Preferably for Linux and/or Windows.
Not sure about what you mean by "fun" but Peter Gottschling's Discovering Modern C++ An Intensive Course for Scientists, Engineers and Programmers is pretty good. For a catalog of shiny new C++ features Marius Bancila's Modern C++ Programming Cookbook is comprehensive.
Is anyone still using the Boost libraries?
Yes! Boost.Asio is still the go-to networking library. (It's also available as a standalone, though.)
Some parts have been adopted by the C++ standard library and can thus be considered obsolete, there a still quite a few goodies!
I'm of two minds when I see comments complaining about header files. Practically speaking, I think "have the preprocessor copy & paste source files together" is a bit of a hackjob, but, conceptually speaking, having your interface and implementation separate is ultimately a good thing.
The problem of course lies not with header files, but C++ the language, as all public fields and private fields must be specified in the class declaration so that the compiler knows the memory layout. It's kind of useless in that sense. You can move private methods out to a separate source file, but, you don't gain much in doing so, at least in terms of strict encapsulation. And of course, if you use templates at all, you can no longer even do that. Which is its own can of worms.
Unfortunately, none of these problems are problems that modules solve. Implementations very much disagree on interfaces vs implementations, precompiled vs simply included, etc etc. In my own usage of modules I've just found it to be header files with different syntax. Any API implemented via modules is still very leaky - it's hard to just import a module and know what's truly fair for application usage or not. You still ultimately have to rely on documentation for usage details.
At the end of the day I don't really care how the implementation puts together a particular feature, I care about how it affects the semantics and usability of the language. And modules do not really differ in proper usage from headers, even though the whole backend had to be changed, the frontend ends up being the same. So it's net nothing.
All said and done, when it comes to defining library APIs, I prefer C. No public/private, you just have some data laid out a particular way, and some functions to operate on it. The header file is essentially just a symbol table for the binary code - and said code can be a .c file or a .o file or even a .a or .lib or .dll or whatever - C doesn't care. Raw functionality, raw usability. No hoops.
I’m not for sure auto is an improvement. I know it is required for lambdas and it makes it easier to type out a very verbose type, but it really does reduce code readability.
I’ve even seen developers use it instead of bool, which is pretty laughable as the they are the same number of characters.
A verbose enough type - and C++ has plenty of those - is indistinguishable from line noise.
There are places where having an explicit type annotation can improve readability, places where it harms readability, places where it doesn’t make much difference one way or another. Giving us the option has been a blessing. All programming calls for good taste, C++ programming calls for it more than most.
Auto is an improvement for C++ only because of its uniquely unergonomic type system and standard library. I'd very much prefer writing something like `iterator<auto>` instead of `auto` or `std::map<lotsofchars>::iterator` and not be told by every linter to change most explicit type declarations to `auto`.
> but it really does reduce code readability.
How about not specifying the type, and letting the compiler infer it correctly and error out when it cannot - like so many other languages do? And those languages are much stricter about types than C++.
And auto reducing code readability? Having to figure out the intricacies of a detailed type to write was a huge barrier, and virtually anyone reading the code with a type involving several nested angle brackets would not bother mentally parsing it anyway.
I think it does reduce readability in some scenarios.
For instance: const auto& processes = getCurrentlyRunningProcesses(); for (const auto& process: processes) { // Ok, what do I do with process now? Is it a pair from a map? A struct from a vector? // If it's a pair from a map, is the key the pid, a unique id, something else? }
std::unordered_map<Pid, ProcessData> is more readable than auto here IMO: you don't need to open the definition (or hope your IDE correctly display the type).
I remember reading something here recently about auto causing some painful and difficult to diagnose bug - I think string was what they thought the type should be (and some implicit cast would have made it a string if the type was specified)... but instead it created a string_view which went on to be used somewhere that accepted both string and string_view and then something tried to use it later but whatever the string_view was pointing to was gone (or something in that vein - I don't recall exactly).
It's probably auto when you wanted auto&.
A copy was made instead of a reference. I've been bitten by that.
IMO the way to make C++ fun is to:
Example: I use an actual editor, and unit tests, but essentially shell is my REPL for C++. It’s easier to figure out that way.Newer features like constexpr have subtle rules, so it’s easier to just try it (even though I’ve used C++ for many years). I run all the tests with Clang too.
---
Example with ASAN:
It’s not too hard to learn to read the output, and then basically you can go nuts like C++ is Python. For a small program, the edit/run cycle will be extremely fast, like Python.The silent undefined behavior is a big barrier to learning, and this removes most of it. (You can also pass -fsanitize=undefined for UBSAN, which finds other bugs, but many fewer IME)
ASAN is built into compilers; you don’t need to install anything. A bare Debian or BSD system has all this good stuff :-)
(copy of lobste.rs comment)
The problem with any language trying to replace C++ for larger codebases is that it's not half as powerful as C++.
I've often cursed in C# because something that could be done trivially in C++ if impossible and causes the dev to create convuluted C# while it could be trivialy done in C++ due to its very expressive language features.
Those 0.1% of the time that you need those extreme features are what makes or breaks the language in PRODUCTION.
I've had the exact same experience, but opposite. Tons of things that are trivial in C# take a ton of code in C++ to me. Maybe it's just going from being an expert in one language to a newer language?
This often comes from expecting C# to be just like C++, where-as more complex use cases are often expected to be done in a (sometimes completely) different way there. It's a good idea to try not to fight the language and work with the way it exposes its features.
My experience was just like yours - easy to move between C and C#, or Rust and C#. But attempting C++ implementation was always far more difficult. It was never worth it over just spending extra effort in either alternative.
If GP reply author has C#-specific questions I'd be happy to answer or point him or her in the right direction. C# is a language with strong systems programming capabilities but has its own learning curve.
What are some examples of these C++ features you've missed in other languages?
Zig not having operator overloading makes it suck horribly for writing any kind of vector code. If everyone had to write int a = int_add(int_mul(3, 7), 2) etc there would rightly be a riot, but since they're not 3D coders they just don't give a shit. Too bad, Zig looks great.
Sorry, one more thing to add to this: Andrew Kelley is obviously a genius, and his talk introducing Zig[0] is in my top 10 of all time technical presentations, for many reasons. But I really do wish someone close to him with a passion for how coding is in many ways applied mathematics, would ask him to please have broader algebraic support for basic operations like +, -, * and maybe divide, with their basic dataflow characteristics. Optimal speed for complex numbers vs std::complex out of the box would be attractive.
I understand his point about not wanting to allow every random C++ feature, but in these cases, it isn't a C++ feature, it's language-level basic algebra.
In C++ land, ISPC[1] is often what you use when you want top speed rendering perf on SIMD CPUs, e.g. Moonray project[2]
Please, just go ahead and define a nice clean API for vectors and scalars like OpenCL provides on its beautiful reference cards: https://www.khronos.org/files/opencl-1-2-quick-reference-car...
[0] https://www.youtube.com/watch?v=Gv2I7qTux7g
[1] https://ispc.github.io/
[2] https://openmoonray.org/
Final edit sorry: in the end I love C++ and have been learning Rust mainly out of curiosity. Avoiding C++ quirks one can have few problems and a great time.
>I've often cursed in C# because something that could be done trivially in C++
e.g?
That claim certainly warrants a serious example. Perhaps you mean "something ... requiring maximum performance ..."?
I returned to C++ after 20 years to program ESP32 microcontrollers. The Arduino Framework is compiled with C++ 21... wait, what? Well that's the value of __cplusplus = 202100L
Glad to find the standard library progressed so much to make C++ more similar to modern languages, but sometimes I wish there was a simpler way to access the std namespace. Any proposal to replace "std::" with $
C++ is fun, but "Rust weirdos" are right. Don't use C++, use Rust. That's all.
I was C++ dev for 5 or 6 years, up to the late 2000s.
I got another C++ job about 3 years ago but bailed after about a year.
I could write a tome about what I dislike but to start with, any language that lacks a working standard built-in string type, is just a hard no for me at this stage in my life. Life is just too short.
The tooling and IDE support is atrocious, no standard dependency management for 3rd party libraries and CMake makes maven look well designed.
I tried to pull my knowledge up to date. Hmmm, we used to have lvalues and rvalues, what's this prvalue thing?
Surely cppreference can explain:
> a prvalue (“pure” rvalue) is an expression whose evaluation
> - computes the value of an operand of a built-in operator (such prvalue has no result object), or
> - initializes an object (such prvalue is said to have a result object).
> * The result object may be a variable, an object created by new-expression, a temporary created by temporary materialization, or a member thereof. Note that non-void discarded expressions have a result object (the materialized temporary). Also, every class and array prvalue has a result object except when it is the operand of decltype;*
> The following expressions are prvalue expressions:
> a literal (except for string literal), such as 42, true or nullptr;
> a function call or an overloaded operator expression, whose return type is non-reference, such as str.substr(1, 2), str1 + str2, or it++;
> a++ and a--, the built-in post-increment and post-decrement expressions;
> a + b, a % b, a & b, a << b, and all other built-in arithmetic expressions;
> a && b, a || b, !a, the built-in logical expressions;
> a < b, a == b, a >= b, and all other built-in comparison expressions;
> &a, the built-in address-of expression;
> a.m, the member of object expression, where m is a member enumerator or a non-static member function[2];
> p->m, the built-in member of pointer expression, where m is a member enumerator or a non-static member function[2];
> a.*mp, the pointer to member of object expression, where mp is a pointer to member function[2];
> p->*mp, the built-in pointer to member of pointer expression, where mp is a pointer to member function[2];
> a, b, the built-in comma expression, where b is an prvalue;
> a ? b : c, the ternary conditional expression for certain b and c (see definition for detail);
> a cast expression to non-reference type, such as static_cast<double>(x), std::string{}, or (int)42;
> the this pointer;
> an enumerator;
> a non-type template parameter of a scalar type;
> a lambda expression, such as [](int x){ return x * x; };
> (since C++11)
> a requires-expression, such as requires (T i) { typename T::type; };
> a specialization of a concept, such as std::equality_comparable<int>.
> (since C++20)
> Properties:
> Same as rvalue (below).
> A prvalue cannot be polymorphic: the dynamic type of the object it denotes is always the type of the expression.
> A non-class non-array prvalue cannot be cv-qualified, unless it is materialized in order to be bound to a reference to a cv-qualified type(since C++17). (Note: a function call or cast expression may result in a prvalue of non-class cv-qualified type, but the cv-qualifier is generally immediately stripped out.)
> A prvalue cannot have incomplete type (except for type void, see below, or when used in decltype specifier).
> A prvalue cannot have abstract class type or an array thereof.
Yeah, this language is loads of fun. I've worked on compilers, interpreters, implemented extended Hindley-Milner type systems, etc. so normally love reading formal language specs but this is just insane.
I recall not being very effective with C++ for years, and then someone recommended the book Large Scale C++ Software Design, and that was a big unlock.
I didn’t use 80% of what’s in the book, but just having a comprehensive way of structuring the code was a massive productivity boost. Looking back, I suspect it was less that it was “the right way”, but just that it was “a way” and most of the benefit was it kept me from overthinking and got me to work.
Later with C++11, I kept having this thought, “in Python this would be way less verbose”, and I started writing C++ that looked more like Python, creating whatever helper functions Python would have (mostly simple stuff, string handling, etc).
That was one of the most productive seasons of programming I ever had, and I still get tempted to write stuff in C++ that Python is better suited for, just because the benefit of not overthinking is that significant (at least for me).
Agree. Sure there are problems here and there but I think that overall modern C++ is likely the most versatile tool in the "compiled to native, can do anything" family
C++ is great, but I hate .h files.. I'm kind of shocked that some tool to automate writing .h files isnt the the standard.
> C++ Is An Absolute Blast
No, it's one of the worst languages I ever used. Tons of footguns and bad design choices everywhere. Too much cognitive load for less benefit than other languages.
I'm surprised the article didn't mention <iostream>. The f.fail(), f.eof(), f.flags() are confusing and verbose. Even something as simple as f.read() doesn't return the number of elements read, so you need to make a separate call to f.gcount(). And then there are all the opaque types like std::streamsize, std::mbstate_t, etc., where you have no idea how their sizes relate to language types like int/long/etc. or fixed-width types like int32_t/uint64_t/etc. https://en.cppreference.com/w/cpp/string/char_traits
And then there are the redundancies. int x = 0; int x(0); int x{0}; all roughly do the same things but have subtle differences in more advanced use cases. This recent thread ( https://codereview.stackexchange.com/questions/294784/c20-ro... ) reminded me that `typedef` got replaced by `using`. A while ago, I came up with a long list of near-duplicate features: https://www.nayuki.io/page/near-duplicate-features-of-cplusp...
> JavaScript still can't even figure out what a for-loop is
ECMAScript 6 added the for-of loop, which is the more useful alternative to the for-in loop.
> C++ has lambda, and it's not bullshit like Python's lambda
C++ lambdas have a heavier syntax than any other lambda I know of (e.g. Python, Java, JavaScript, Haskell, Rust), because it needs to specify attributes and captures. https://en.cppreference.com/w/cpp/language/lambda
> My thinking is C++ is now about as good as any other language out there
Not by a longshot. Instead of C++, I reach for Java if I want fast design time, safe operations, and a more limited set of tools (e.g. not needing to decide how many layers of pointer indirection I want). I reach for Rust if I want the power of C++ without its footguns.
Heck, my motto for Rust has always been, "C++ done right". Every time I compare analogous features in C++ and Rust, I find that the Rust version is much better designed. As the simplest example, in Rust it's a compile-time error to use a variable whose value is moved out, whereas in C++ the variable is still usable but has an invalid value. Another example is that Rust has traits but C++ relies on instantiating templates and then "duck-typing" to see if the resulting code can actually compile. And let's not forget nullptr, the trillion-dollar mistake - C++ makes nullptr implicitly part of every pointer(*) type, but Rust bans it by default unless you opt in with Option<T>. Rust has other quality-of-life features such as easily declared tuple types, the unit type instead of void (which makes functional programming easier as you don't have to special-case void), pattern matching and unpacking, methods on primitive types (e.g. 456u32.isqrt() instead of sqrt(456)). I just can't look at C++ seriously when Rust is miles ahead, being more expressive and safer.
> The Amazing Comeback of C++11
I will agree with this in a limited sense When I write C++ code (because I'm a masochist), I will not tolerate anything less than C++11, because C++03 and C++98 are much, much worse. I'm talking about things like various types, standard library classes/functions, unique_ptr, and move semantics.
I'm mostly very much in agreement with what you've said here but I want to pick on a few things:
> Instead of C++, I reach for Java if I want fast design time, safe operations, and a more limited set of tools (e.g. not needing to decide how many layers of pointer indirection I want).
I don't think I've ever seen a good reason to prefer Java over C# for anything.
> Another example is that Rust has traits but C++ relies on instantiating templates and then "duck-typing" to see if the resulting code can actually compile
Is the https://en.cppreference.com/w/cpp/header/type_traits functionality not sufficient for what you have in mind?
>the unit type instead of void (which makes functional programming easier as you don't have to special-case void)
Why would special-casing be necessary? You don't need to say e.g. that mapping a void-returning function produces an empty result; it could just be a compile error. I feel like void returns should be a special case and I don't like all the ways `None` is used in Python, because it's one of the few things that blurs an otherwise very strong distinction between statements and expressions, analogously between commands and queries.
> in Rust it's a compile-time error to use a variable whose value is moved out, whereas in C++ the variable is still usable but has an invalid value
C++ does it this way because there are common cases in systems code where doing it the Rust way would literally be unsafe. Not all memory references are visible at compile-time and may exist outside the address space.
Would you mind elaborating more on those common cases? I'm not sure I've heard of destructive moves being less safe than non-destructive moves and I'm not smart enough to figure out what you're talking about in your second sentence.
Shared address space. Some other process or silicon can read or write the object you just moved but doesn’t know you moved it. You need to keep the memory previously occupied by the moved object valid long enough for those references to realize you moved it to prevent corruption.
A typical case is high-performance I/O, which uses a lot of DMA. DMA is oblivious to most programming language semantics like lifetimes, ownership, etc and will happily step all over your address space if you aren’t careful.
I'm curious to hear more about this use case. The DMA I do in rust is generally static buffers, because I'm not sure how to pass the borrow checker otherwise. (There are ways). Generally, you set up a static [u8] buffer, and pass its pointer to the hardware that's doing the DMAing. Then magic, then the buffer gets read or written by the hardware. In this scenario, the variables never go out of scope. Am I cheating, and avoiding this issue by using static buffers? If the buffer drops during a DMA transfer, I believe UB happens.
I'm suspicious a similar principle happens with memory-mapped flash memory as well, e.g. QSPI.
Thanks for taking the time to elaborate!
> Some other process or silicon can read or write the object you just moved but doesn’t know you moved it.
That should primarily affect buffers that are inline with the moved object, right? i.e., not static buffers or stuff that's heap-allocated? How common is that scenario? I admittedly generally thought DMA used static buffers, though to be fair I'm not exactly highly experienced in the space.
> You need to keep the memory previously occupied by the moved object valid long enough for those references to realize you moved it to prevent corruption.
How is this (reliably) handled in C++? I feel there's gotta be more than just hoping the empty object hangs out long enough for the rest of the system to catch on (e.g., moving things around near the end of a scope when the empty object will be destroyed "soon").
This just means that affine types aren't the right tool to model memory that you don't have full control over. Which is true, but also represents a very small subset of overall data. Rust provides you with other tools to handle those kinds of situations.
There is a small wart here, which is that (with async Rust) some of these use cases would benefit tremendously from full-fledged linear types, or at least an easy way to run code during async cancellation.
The difference between an affine and a linear type is that the ways in which a linear type is consumed are controllable through encapsulation — for example, imagine you have a type which represents a certain amount of money, and you want to statically prevent the money from being dropped on the floor. Affine types don't prevent that statically, but linear types do. You can still have runtime checks though.
You are correct wrt to iostream, it's bad. I stick to studio.h.
> No, it's one of the worst languages I ever used.
That says more about you than the languages you've used.
C++ is one of the top 5 languages used in production. This is true still today, with so many specialized languages to pick and choose. No one had to hold a gun to anyone's head to get them to adopt it. How do you rationalize that if your opinion had any substance or merit?
For the sake of argument, I assert exactly the opposite: C++ post-C++11 is the absolute best language ever devised by mankind, bar none. Am I wrong?
> Tons of footguns and bad design choices everywhere.
Please go ahead and point out the single most egregious "foot gun" or bad design choice you can possibly imagine. The worst. This will serve to show the world how well thought through your opinion actually is.
> No one had to hold a gun to anyone's head to get them to adopt it. How do you rationalize that if your opinion had any substance or merit?
We did. It was either C or C++ that were supported by our hardware vendor.
> For the sake of argument, I assert exactly the opposite: C++ post-C++11 is the absolute best language ever devised by mankind, bar none. Am I wrong?
Absolutely. It is one of the most complex and error prone languages out there.
Lambdas that can borrow from the stack and still return. Absolutely unacceptable feature without a borrow checker.
I don't think C++ is one of the worst languages; there are very few languages as powerful as C++, that alone makes it one of the best.
But, much like love and hate, I also don't think that the opposite of good is always necessarily bad, nor vice-versa. A language can be both good and bad at the same time, in different aspects.
C++ is really good (unrestrained freedom, performance, ecosystem), and also really bad (tooling, templates, really hard to debug memory issues).
Rust is somewhat less good (less free, slower, puny ecosystem in comparison), but also a lot less bad (powerful type system, thread safety, fearless iterators/lambdas, etc).
Many of the warts C++ has to carry due to its commitment to compatibility, are fixed in Rust with much better alternatives. A lot of footguns are well encapsulated in Rust's affine-ish types and algebraic data types, while still providing unsafe hatches for when you need them. Defaults really matter.
[dead]
I had a similar reaction returning to C++ a few years ago, and getting exposed to stuff like lambdas, smart pointers, and template metaprogramming painpoints being fixed. One especially nice thing about C++ is GCC, which lets you use relatively recent features even on obscure HW/OS architectures that other, trendier languages ignore. For example, you can use modern C++ features, at least upto C++17, on OpenBSD.
>But, C++ kept evolving, and the standards committee seemed to realize that if they don't do something they'll become an obscure language only used by a single 10 trillion dollar industry. How pathetic!
Which industry is this referring to?
My guess is that it's the game industry, because it's probably worth that much and is almost pure C++.
The stuff you use for work is not fun. The stuff you use for fun is fun. There, I solved the mystery for you.
If you choose technology for work by what is the most fun - you enter a hedonist treadmill. Stop. JS framework insanity lies that way. No cool technology will save you from burnout.
The amount of high performance, production grade, massively tested libraries written in C++ is unbeatable. I will be honest here, it's easier to improve C++ security by implementing a compiler that produces safer C++ (like Typescript to Javascript) than rewriting everything in any other language (Rust, Zig, Odin, whatever).
I mean, could you estimate the cost ($ and time) it would take to rewrite the best audio framework in any other language? (https://juce.com/).
Zig is great, it’s not aiming to be a replacement for C++ though. One of the awesome things about Zig is its interoperability with C. In that sense it’s more of a Typescript to C than Rust is to C++. I’m still not sure what I think about Rust personally. In my region of the world I suspect it’ll continue to struggle to find any form of adoption as the C++ people seem to have very little interest in trading their decade long experience for Rust. Zig on the other hand is winning the hearts and minds of most C programmers I know.
I hope Rust succeeds though. I say this more from a change management perspective than anything else. It’s extremely hard for us to find developers who will primarily work with garbage collected languages but occasionally have to work with either C or C++ when bottle necks appear. Rust makes that much easier, or perhaps less dangerous would be a better term. I’m not sure any of the attempts at making C++ more safe to use is going to really succeed in this regard. Maybe, but I nothing within the C++ community seems to pull in that direction so I doubt it. I’d like to mention that I’m aware that Zig isn’t helpful in this regard either as it’s not memory safe.
How many total lines of code do you imagine are in these libraries, compared to all their clients? If rewriting the libraries sounds like an unreasonable amount of work in a world where all that client code exists, doesn't that reflect negatively on the readability of C++?
Every time I try to use Rust instead of C++, I'm left asking "bruh, do you even functor?"
Tl;dr programming as a hobby building what and how I want is much more fun than doing it for money and being told what to do.
[flagged]
[flagged]