No, it's one of the worst languages I ever used. Tons of footguns and bad design choices everywhere. Too much cognitive load for less benefit than other languages.
I'm surprised the article didn't mention <iostream>. The f.fail(), f.eof(), f.flags() are confusing and verbose. Even something as simple as f.read() doesn't return the number of elements read, so you need to make a separate call to f.gcount(). And then there are all the opaque types like std::streamsize, std::mbstate_t, etc., where you have no idea how their sizes relate to language types like int/long/etc. or fixed-width types like int32_t/uint64_t/etc. https://en.cppreference.com/w/cpp/string/char_traits
> JavaScript still can't even figure out what a for-loop is
ECMAScript 6 added the for-of loop, which is the more useful alternative to the for-in loop.
> C++ has lambda, and it's not bullshit like Python's lambda
C++ lambdas have a heavier syntax than any other lambda I know of (e.g. Python, Java, JavaScript, Haskell, Rust), because it needs to specify attributes and captures. https://en.cppreference.com/w/cpp/language/lambda
> My thinking is C++ is now about as good as any other language out there
Not by a longshot. Instead of C++, I reach for Java if I want fast design time, safe operations, and a more limited set of tools (e.g. not needing to decide how many layers of pointer indirection I want). I reach for Rust if I want the power of C++ without its footguns.
Heck, my motto for Rust has always been, "C++ done right". Every time I compare analogous features in C++ and Rust, I find that the Rust version is much better designed. As the simplest example, in Rust it's a compile-time error to use a variable whose value is moved out, whereas in C++ the variable is still usable but has an invalid value. Another example is that Rust has traits but C++ relies on instantiating templates and then "duck-typing" to see if the resulting code can actually compile. And let's not forget nullptr, the trillion-dollar mistake - C++ makes nullptr implicitly part of every pointer(*) type, but Rust bans it by default unless you opt in with Option<T>. Rust has other quality-of-life features such as easily declared tuple types, the unit type instead of void (which makes functional programming easier as you don't have to special-case void), pattern matching and unpacking, methods on primitive types (e.g. 456u32.isqrt() instead of sqrt(456)). I just can't look at C++ seriously when Rust is miles ahead, being more expressive and safer.
> The Amazing Comeback of C++11
I will agree with this in a limited sense When I write C++ code (because I'm a masochist), I will not tolerate anything less than C++11, because C++03 and C++98 are much, much worse. I'm talking about things like various types, standard library classes/functions, unique_ptr, and move semantics.
I'm mostly very much in agreement with what you've said here but I want to pick on a few things:
> Instead of C++, I reach for Java if I want fast design time, safe operations, and a more limited set of tools (e.g. not needing to decide how many layers of pointer indirection I want).
I don't think I've ever seen a good reason to prefer Java over C# for anything.
> Another example is that Rust has traits but C++ relies on instantiating templates and then "duck-typing" to see if the resulting code can actually compile
>the unit type instead of void (which makes functional programming easier as you don't have to special-case void)
Why would special-casing be necessary? You don't need to say e.g. that mapping a void-returning function produces an empty result; it could just be a compile error. I feel like void returns should be a special case and I don't like all the ways `None` is used in Python, because it's one of the few things that blurs an otherwise very strong distinction between statements and expressions, analogously between commands and queries.
> in Rust it's a compile-time error to use a variable whose value is moved out, whereas in C++ the variable is still usable but has an invalid value
C++ does it this way because there are common cases in systems code where doing it the Rust way would literally be unsafe. Not all memory references are visible at compile-time and may exist outside the address space.
Would you mind elaborating more on those common cases? I'm not sure I've heard of destructive moves being less safe than non-destructive moves and I'm not smart enough to figure out what you're talking about in your second sentence.
Shared address space. Some other process or silicon can read or write the object you just moved but doesn’t know you moved it. You need to keep the memory previously occupied by the moved object valid long enough for those references to realize you moved it to prevent corruption.
A typical case is high-performance I/O, which uses a lot of DMA. DMA is oblivious to most programming language semantics like lifetimes, ownership, etc and will happily step all over your address space if you aren’t careful.
I'm curious to hear more about this use case. The DMA I do in rust is generally static buffers, because I'm not sure how to pass the borrow checker otherwise. (There are ways). Generally, you set up a static [u8] buffer, and pass its pointer to the hardware that's doing the DMAing. Then magic, then the buffer gets read or written by the hardware. In this scenario, the variables never go out of scope. Am I cheating, and avoiding this issue by using static buffers? If the buffer drops during a DMA transfer, I believe UB happens.
I'm suspicious a similar principle happens with memory-mapped flash memory as well, e.g. QSPI.
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.
> 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").
> 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.
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.