C++ is an absolute blast
learncodethehardway.com
learncodethehardway.com
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.
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.
Even startups seem to be less fun. It seems the VC world foments a narrow window of thought with a lot of copy cat startups doing the same thing. Fads, lots of fads.
I use C++ for every new project because I know if I need to build something myself, I can do it. Using frameworks all the time sucks because frameworks never work the way you want. Especially if it deals with the core of the project.
If you can build it yourself, then do it, otherwise use a framework. And if you can build it yourself, C++ is a great tool because it can do everything and doesn't get in your way in all the important ways. Meaning, I can express my mental model directly in C++, where a language like Rust might complain I'm doing an unsafe thing. Sometimes I think in pointers damned and pointers are a great thing!
Lets bring fun back! Not just in our personal lives, but work too!
People like you baffle me to no end and make me feel I've lived in a parallel universe my entire life -- when I read a comment like yours. How in the everliving frak have you people survived?
Almost as if country and environment have anything to do with it, more than merit.
You get paid to solve problems not write software, and having fun is critical to solving problems. If you are bored, stressed, and frustrated, what makes you think your mind would be in a good place to solve problems?
Assume I had no choice of the programming jobs I was taking in the last years. Now try to fit your framework of mind in it.
Present me the result as a textual reply here?
Hint: what I felt and how I felt... nobody cared.
Environment and even geographical area count. A lot. Even if I worked remotely for the last 14 years. Turns out USA companies really don't want people outside of USA, especially recently.
I'm sorry you suffered.
Hopefully I wisened up and I think I started doing the right steps but it's like blowing against the wind; ultimately you'll succeed but... yeah, nevermind. Don't want to complain. I can muscle through it. Not like I have any other meaningful choice either. It's a victory or death situation (not literally but almost).
Thanks for your sympathy. <3
Hope I didn't come across as too much of an arse. I am simply way too jaded and burned out.
If I couldn't have fun at work (of this exact nature) - I'd quit, I'd be bored, my brain would be constantly itching.
These activities are no more a waste of time than reading up on a topic or doing a tutorial or anything that furthers your skillset. A boss that objects to that wouldn't last long with me.
See, you're privileged as hell if this doesn't even cross your mind.
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 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.
OK, I want a job where I have fun and I am programming. Starting at $150K / year. Are you hiring? No deadlines, no commercial targets. Let's have a casual interview during which I'll prove I've been coding ever since 12-year old.
Let's go.
It's amazing that we ended up with the javascript equivalente of autotools.
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.
NodeJS + express + vanilla web components is the most graceful and productive stack I've ever used in 30+ years of development.
I do have to admit at least some of the pain is self-inflicted on my part. I _want_ those nice abstractions, so I will write a damn Rollup or Babel plugin if it means I can use Sass stylesheets in my TypeScript+JSX components that compile to vanilla Custom Elements (so I don't have to depend on a huge framework runtime).
But as nice as the Babel/Rollup/Vite plugin APIs are, when you start doing stuff like that you then have to deal with all the deep-in-the-weeds bullshit that comes with it, like the fact that Webpack and Rollup and Node.js all have subtly different (and mostly poorly documented) module resolution rules and that they all differ wildly from the official ES-module spec that the standards committee finally shipped like 20 years too late, so trying to get your unit-testing framework and your bundle toolchain and your browser to all agree about how to digest the mess you've made becomes this giant clusterfucktastrophe that makes you question every life decision and formative event that led you to the moment you thought this might be a neat idea.
But yeah just like build a React app or whatever and you'll probably be fine.
The JS ecosystem absolutely has the problem of "too many compilers", and of course as I'm typing this there's someone out there writing a "one compiler to unify them all".
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:
package_name = { git = "https://github.com/user/repo", rev = "revision" }
and you're done, you can use it as a dependency.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.
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.
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?)
- 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
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.
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.
Which is not disagreeing with you just because you are explaining/disputing the reason it was a PITA.
> 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.
Which I also find fundamentally wrong because most real world projects have a C/C++ dependency which needs to be built and is built from one of those languages build systems by calling into cmake / autotools + make.
Just because you called apt-get or any other way to pull dependencies doesn't remove the fact that they need to be built and likely involve a C compiler.
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...
I found it by looking at the go package because they claimed they are statically linking there.
That basically never happens, which is the point.
CMake is a pain.
Typically you statically link against .a object library. .so are shared objects intended for dynamical linking only. Does duckdb provide something like duckdb.a ?
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.
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
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.
(Admittedly there are languages like python and ruby that buck this trend.)
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.
[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/
It is not easy to learn (I have no idea why it's considered a beginner friendly language). It benefits from putting in work to learn it. It's not as bad as c++, but it is not a shining example of how to get it right either.
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.
Normal day in cpp land...
> Probably the two most useful features added to C++20 are requires and requires
said without a trace of irony ... the whole thing reads like parody!
I keep hearing that C++20 or whatever is getting so great but if so why would they specify such a dumpster fire of syntax???
What a horrible idea that one might no longer be there.
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.
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.
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.
Essentially, the warts are too large and numerous for me to find any inner beauty in it any more.
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).
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.
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!
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.
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.
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.
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 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.
"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.
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.
> 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
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?
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.
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.
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."
I think you would have a hard time convincing the C++ standards committee to put a checked container in the standard, maybe now with the negative publicity maybe but definitely not before.
I'm guessing it would be impossible to get an unchecked container into the rust stdlib.
There clearly are people who think that UB isn't as dangerous as I do, so if that's where you stand, I guess it is just a "fundamental difference of opinion". If you actually believe that you (or anyone else) is capable of being careful enough that you aren't going to accidentally write code that causes undefined behavior, then I don't think you're wrong as much as delusional.
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?
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.
Note that this is not something C++ is able to do, and C++ has notoriously subtle iterator invalidation rules as a consequence.
Note that this is not something C++ is able to do
Or you could just not mutate the container.
They said "bounds-checking penalties are small" not that it was free. You've described a situation where bounds checking happens and declared that it "should be" expensive because it happens. You haven't given evidence that it's expensive.
You can profile it yourself, but it is the difference between direct access to cached memory (from prefetching) and two comparisons plus two branches then the memory access.
To someone who doesn't care about performance having something trivial become multiple times the cost might be acceptable (people use scripting languages for a reason), but if your time is spent looping through large amounts of data, that extra cost is a problem.
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. 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.I think if you adopted a more defensive programming style where you check your values before dereferencing them, handle all your error cases, you might find C++ is not so scary. I would also recommend not using auto as it makes the types less clear.
std::optional<int> v = std::nullopt;
if (v == std::nullopt) {
return std::unexpected("Optional is empty.");
}
std::println("{}", *v);
If you are dereferencing things without checking that they can be dereferenced I don't know what to tell you.Perhaps, 3 types could exist for 3 options.
if x.is_some() {
let x_value = unsafe { x.unwrap_unchecked() };
}
everywhere.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.
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.If I really need the low-level control, I can wrangle C (warts and all), otherwise Rust, Python, etc just make me happier.
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.
I'm afraid you underestimate the will power of rustaceans to find literally anything to rewrite.
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.
Also there is the whole point Objective-C and C++ are much better than those improvements will ever be.
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-...
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.
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.
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.
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...
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++.
Also present day javascript (the language; the ecosystem is another matter ;) compared to the 'var' and IIFEs-for-scoping era.
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?
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.
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.
Hey dude, I can’t get this thing to compile?
Just wrap all your variables in Arc; that’s what I always do.
https://leanpub.com/cppinitbook
https://www.reddit.com/r/ProgrammerHumor/comments/8nn4fw/for...
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.
Is this your best reference?
>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.
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.
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.
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.
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.)
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'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.
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.
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.
Now, based on the new thread killing implementation, a malicious thread can just catch the exception and keep running, and on top of that the Security Manager is being removed too.
So now my options are, switch to C++ and Lua, or run every sub-component in its own VM and add a bunch of IPC in the middle of my application. Or maybe port it all to JavaScript. Massive headache, performance implications are bad and it breaks continuity in an open source community that's been running since 2001.
This is my use case, maybe hello world 101 doesn't have any problems but I'm sure anyone doing more complex stuff is going to hit the same kind of issues.
I don't know about security manager though. I haven't poked at it for a while, but I wasn't aware that they removed it? Maybe you just need to opt I to the JVM access rule to support it. You can always layer your security with a separate class loader, which can prevent child tenants from even seeing protected classes and statics, which is always good security layering if untrusted is your problem.
So it definitely is old enough to fit and it certainly has it's warts from age that show up in long lived projects.
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.
Sadly, `std::regex` is anything but good (poor performance, lack of Unicode support) and should be generally avoided.
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.
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?
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.
I would expect a lot of unsafe though.
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]:
So I believe gamedev will stay on C++ the longest
Game crashes with segfaults for 0.001% users? So what?
Here are a bunch of C++ annoyances I can think of. Library/Package management is nonstandard. Headers are code duplication. The standard library changes depending on implementation and is almost undreadable. Weird behavior in the standard. People relying on undefined behavior without realizing. Use before assignment issues. Subtle ownership issues and memory leaking due to bad refcounts. Needing to make everything const instead of by default. Checking for exceptions in everything you call to ensure your code is noexcept. Unreadable errors when working in the standard library. Heavily OO code is basically tech debt. The LSP is not structural like in Rust where the definition is found by checking the AST. Navigation and codebase discovery is slow in C++ because of the poorer LSP.
Rust has first class explicit 'nostd' support whereas in C++ you need compiler specific flags to disable the std lib and its hard to make sure you did it right. So the embedded reasoning is silly to me.
Rust also has game engines like bevy but they are new. You could hook into godot scripting with Rust if you want. Low level audio is just as easy in Rust and you can do it cross platform with a single crate.
In general I think it's just legacy code and hesitancy to change.
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.
Nor is cmake even really a standard: after thirty-odd years writing C++, I've yet to work on a project which used it!
Of course CMake has only really been a practical tool for 20-ish of those 30-odd years, and "Modern CMake" was only published twelve years ago; it's definitely gained a lot of popularity over the last decade, but the idea that CMake is the default C++ build system, not just one among many, still feels to me like a recent change.
At one point I was using gulp.js to build my small C++ experiment.
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'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.
>It's about writing the constructor in such a way that you can always safely destroy the object.
I agree, but I see this as a good thing, even though it makes the class slightly harder to implement than it might be if responsibility for getting the object into a state safe for destruction is dumped on (or even shared with) the calling code. The latter approach requires programmer discipline at every use -- but you will only implement the class once, and probably use it many times.
Every time I write C code.
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 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.
One problem is that most C compilers accept implicit conversion between unrelated pointer types by default. If you accidentally pass a `const T*` to a function that takes a `T*`, you only get a warning. In C++ this is always a compiler error.
Another thing that I find very annoying (that is more about `const` in general): in C you cannot use `const` variables as compile time constants, instead you always have to use the preprocessor.
> but if you program C more you should have a go-to library or your own implementation.
Sure, but I still don't like the fact I need to find a third-party library (or roll my own) for the most basic data structures. Also, it's not like generic containers can be trivially implemented in C. They require lots of preprocessor magic and the resulting API will always be much worse than any equivalent C++ implementation, in particular with non-trivial types.
As for compiler time constants, you certainly can use them for those, but the compiler will happily identify constants without the const keyword, whose true purpose is to cause a build time error if you try to modify it.
Regarding libraries, see my other reply:
https://news.ycombinator.com/item?id=42506570
Libuutil is a system library that is on any system that uses ZFS, although there are no known external consumers, so if you are interested in being the first, feel free to open bug reports with OpenZFS asking for removed functionality to be restored if you want any of it.
No, you can't. In C, `const` variables are not constant expressions. This means you can't use them in case labels or to define the size of an array.
> Regarding libraries, see my other reply:
I know that there are several container libraries for C. I was only complaining that there is no standardized solution for the most basic data structures.
I do not agree about the APIs being worse than C++.
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.
I still dont think any programming language today capture what we had in the late 80s and 90s. But may be that is just nostalgia.
- 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.
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.
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 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).
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.
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.
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.
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.
- 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 a middle layer, without having to keep repeating all the security flaws of coding in plain C.
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).
Can anyone point me to a learning reference that will let me jump the meta programming apocalypse and just get to the good stuff?
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`
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 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
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.
(T1, ..., Tn) Apply<T1, ..., Tn>((Func<P, T1>, ..., Func<P, Tn>) args)
This is not possible in C# because the language doesn't have variadic generics. Instead, I used runtime reflection and wrote something like this: object[] Apply(Func<P, object>[] args)
Although it worked, the downside is that the types T1, ..., Tn are no longer statically known, which means that the function's contract has to be written in comments and the caller has to check them manually. In contrast, C++ has variadic templates, which would allow the compiler to check the types automatically.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.
e.g?
But there's a downside to C++ as I still see it now: steep learning curve in tooling. I am used to work on legacy projects with long lifespan (mostly infinite, not that I look at it :) ) and kinda got lost when tried to contribute to a modern project (I'd call it a hipster project): the amount of abstractions in build/management tools was terrifying.
CMake, for example, is and abstraction over an abstraction above another abstraction. And they used an abstraction over CMake! It also required Python. And not the one available in my OS.
I admit, I'm more used to cursed MSVC projects and simple Makefiles. But when CMake starts its dirty work, it's like a black hole of weird scripting and endless pulling of hopefully compatible dependencies you can hardly control (yeah, where does MSVS store that pulled and precompiled garbage? Oh, shi- it's in %userappdata%??? what about other users? What if I want it on a different drive, like MY project?). CMakeLists.txt has a syntax of it's own, and a really obscure one: is it declarative? Are those commands? When does the order of lines matter? Why are CMakes so incompatible? What are those vars? How is any of that online documentation useful? How do I make things work as I want?? And it still doesn't because some required repos have moved, are offline or became incompatible.
The infrastructure surrounding my helloworld.cpp was getting SO immense, but it never built as... in the end I was required to upgrade to Windows 10 or 11, which I'd never do, so... This is the sad non-C++ part of C++ programming that I utterly hate.
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
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.
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.
It all became clear by the end of the post — the author is a Windows user.
My gut feeling wasn't wrong. Give me my 20 minutes back please.
Fun wise though, I get the most fun with dynamic languages that are highly interactive like Forth, Common Lisp and Smalltalk. I don’t have to drop out of the zone to kick the compiler all the time.
That is awesome. I hope if my kids have kids i'll be of some technical use to them too!
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 wonder if Xcode does any better with them. Now that would be something.
It's a bit tongue in cheek but: "Are we modules yet? Nope. ... Estimated finish by: Wed Sep 20 2541"
Having to define header files in C++ is pretty annoying after doing Swift for many years.
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.
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?
Is that supposed to mean anything at all?
They do, except they don't have the bandwagon effect motivating them to complain about other solved problems.
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.
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 mean, could you estimate the cost ($ and time) it would take to rewrite the best audio framework in any other language? (https://juce.com/).
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.
While I understand that not everyone gets to work on a John Carmack level codebase, is working on a C++ project really as challenging and unrewarding as it seems? Is the productivity that low, and do every step feel so difficult that they eventually drive developers away?
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.
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.
Ouch...
Preferably for Linux and/or Windows.
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
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.
I’ve even seen developers use it instead of bool, which is pretty laughable as the they are the same number of characters.
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.
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.
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).
A copy was made instead of a reference. I've been bitten by that.
Some parts have been adopted by the C++ standard library and can thus be considered obsolete, there a still quite a few goodies!
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!
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.
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.
But doesn't that completely ruin the point of the post? I agree with you that something feeling 'fun' is more personal, and that the criteria of what constitutes fun are up to the user. The author doesn't agree with that - you can either adopt the former point or promote the Right Way of having fun. Those snarky remarks made me put this blog into the second category. When you're so invested in your argument, even a fundamentally harmless post about having fun will get that language wars hit piece subtext.
If anything, they seem more like desperate cheap shots than arguments. Other people, the NSA etc dislike unsafe-by-default code? Well, they're just authoritarian anti-fun ideologues! Rust users bring up some of the same criticisms I recall in the last paragraph? Well.. uh... that borrow checker, am I right?
But to get back to the point of the article, for fun solo projects, when the 'mood for coding' comes over, I may be biased, but I think C++ is still the best. It's like when building a prototype. You just want to test your idea and see how it looks and play with it and just worry about bugs and program correctness later. While coding it in Rust you'd have to spend extra time determining the correct memory ownership relations and that can break the flow.
After programming for 20 years, it doesn't come nearly as often as it used to, but I still get that feeling from time to time.
Alan Perlis quote: "I think that it's extraordinarily important that we in computer science keep fun in computing. When it started out, it was an awful lot of fun. Of course, the paying customers got shafted every now and then, and after a while we began to take their complaints seriously. We began to feel as if we really were responsible for the successful, error-free perfect use of these machines. I don't think we are. I think we're responsible for stretching them, setting them off in new directions, and keeping fun in the house. I hope the field of computer science never loses its sense of fun. Above all, I hope we don't become missionaries. Don't feel as if you're Bible salesmen. The world has too many of those already. What you know about computing other people will learn. Don't feel as if the key to successful computing is only in your hands. What's in your hands, I think and hope, is intelligence: the ability to see the machine as more than when you were first led up to it, that you can make it more."
- "Quoted in The Structure and Interpretation of Computer Programs by Hal Abelson, Gerald Jay Sussman and Julie Sussman (McGraw-Hill, 2nd edition, 1996)" via https://en.wikiquote.org/wiki/Alan_Perlis
(Perhaps relevant to this article/overall thread: "Programmers should never be satisfied with languages which permit them to program everything, but to program nothing of interest easily.")
(it is a good smile)
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.
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.
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.
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.
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.
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.
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.
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").
> 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.
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.
Um, std::string is a thing...
A wafer thin wrapper around an array of bytes using null termination - a model of "strings" which is effectively a computer technology fossil - nearly 60 years old at this stage.[1]
It contains no specified or implied encoding - so there is way to actually interpret the data as characters which I guess wasn't a problem in the age before computer networking - your machine has an encoding built in and that was how you interpreted bytes as characters.
A representation that, to save a byte or two at the header, means that determining the length of the string is an O(n) operation.
It's an abstraction so leaky, it's hard to see the advantage over const char* and the leaks can never be plugged given it's part of the spec that c_str() must run in constant time.
It's basically a dangling pointer generator with no unicode or any sort of internationalization support.
But why provide a usable string class (never mind any sort of usable date/timestampe representations) when the language designers can spend years to add a whole new layer of absolutely useless complexity to the language - like concepts, for example.
Nah - life is too short.
Which industry is this referring to?
Love this. This is an old post though right, still talking about C+11?
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 $
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
Example: $ echo 'int main() { return 42; }' > foo.cc
$ c++ -fsanitize=address -o foo foo.c && ./foo; echo $?
42
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 :-)
(copy of lobste.rs comment)
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.
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.