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.
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.
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.
> 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.
_Generic is from C11 though. For array I wonder people don't use them more. After all, array access can easily be bounded-checked in contrast to pointers. I essentially eliminated all my spatial memory bugs this way.
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'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. 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.
> 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.
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.
Thanks, I briefly read the changes on cppreference. It's a good list (although I don't get many of the points as I'm an amateur) but I think my small project doesn't benefit a lot.
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.
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)?
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.
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.
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.
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.
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 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’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.
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).
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`.
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.
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.
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...
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.
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.
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.
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.
> 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.
> 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.
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.
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.
Python has a lot of footguns (and special __ methods), and insane, alien, scoping rules. And lots and lots of syntax, as well.
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.
> 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.
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.
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.
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.
Zig's interoperability with C really is a stroke of genius. That and the fact that it tries to focus on simplicity with no hidden control flow. It's a fine line to walk, because there's always a tradeoff between abstracting things away to make the code more readable, and tucking potentially critical flaws away out of sight. But so many of C++'s edges come from that hidden control flow, and quirks around things like initialization, ownership, and even things like assignment that less experienced programmers take for granted (if they've ever thought about it at all). And I say that as someone who uses C++17 both professionally and privately, and enjoys the language.
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++?
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
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.
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.
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.
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.
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.
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
In the interest of correctness, I just wrote a quick test of the above code at http://canonical.org/~kragen/sw/dev3/intcount.c. Perhaps surprisingly, it works, including the error handling, which is not always the case when you write a bunch of untested C after midnight on your cellphone. But a hash table is evidently simple enough.
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.
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.
You can build with -Werror like the rest of us to get a compiler error instead of a warning.
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.
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.
> As for compiler time constants, you certainly can use them for those
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.
What you want is something is is exclusively for compiler time constants, rather than can merely use compiler time constants. I know I am being pedantic here, but that is an important distinction to make when dealing with computer programming languages, since the descriptions need to be free of ambiguity or there will be problems. What you had meant was unclear to me in your previous remarks. I understand now.
I do not see how this not being an error is a problem. First, you can usually toggle this via a compiler flag. Second, I personally find it very convenient that the compiler does not stop for such things. This gives me more freedom how I can do my work, e.g. fixing const warning or do some other change first.
I do not agree about the APIs being worse than C++.
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.
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.
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.
> 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.
> 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.
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.
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").
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.
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).
>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.
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 jus...
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.
376 comments
[ 1.9 ms ] story [ 283 ms ] threadBasically 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.
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'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
[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.
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?
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`
https://www.youtube.com/watch?v=tn69TCMdYbQ
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.
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.
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.)
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.
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.
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.
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.
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.
Every time I write C code.
https://en.wikipedia.org/wiki/Code_sanitizer
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.
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.
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.
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 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).
Which industry is this referring to?
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.
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 jus...
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.
[1] https://en.wikipedia.org/wiki/B_(programming_language)