Constant folding via constexpr in C++ is much much better than C.
Templates help write generic code that results in specialized machine code; it may be hyper optimized at the cost of binary size. They make it harder to write code that at runtime is a little more type generic (if that makes sense). You can't always afford those additional copies generated. Not that it's a bad thing; just an observation as I write both strictly C and C++ at my day job.
Stealing the below; string handling is significantly safer with std::string. I'm not sold on std::string_view.
That being said, string_view is arguably safer than passing around a char* and a length.
Literally had to write a psuedo borrow checker to stop it: https://youtu.be/80BZxujhY38?t=1095
So much work rather than admit the interface is bad and you should feel bad.
C++: "shoot yourself in the ~~foot~~ face."
I'm not entirely sold on string_view or span yet. But I wasn't sold on tuples either and I use them a lot now.
That being said, yes, string semantics are hard. Passing around copies of a string buffer (std::string) or calling to_string_view everywhere don't seem better.
It's not pretty, but it's not hard either, and gives you full control over how many instances there are around. When C++ is used in a heavily constrained environment, that can come in handy.
These alone are a worthwhile "dialect" on top of C, the use of which results in a far safer C; and—as far as I know—these smart-pointer types and their compile-time semantics would be impossible to implement without C++'s additional layered-on "C with classes" type system.
(It's clear that these smart-pointer types are a valuable addition to C, because they were independently implemented as part of all sorts of other C macro libraries in the early 90s. I think there were at least four C smart-pointer implementations that existed as part of various parts of Windows, for example.)
Manually referencing and de-referencing exactly when needed avoids most of this. Sometimes it's perfectly safe to pass the same reference through several layers without changing the number of references.
As an added bonus YOU get do decide which references, if any, need to support multiple threads.
How is this helping us go anywhere worth going?
I was talking about std::shared_ptr, which is the closest thing to reference counting that STL offers.
My post above was about how C++ smart-pointers compare favorably to Rust's oft-considered-"unique" ownership/borrowing system. The particular STL features that cause equivalent "compile-time magic" to happen to what you get from Rust's semantics, are those of std::uniq_ptr and std::move, not those of std::shared_ptr. So the value (or lack thereof) of std::shared_ptr, isn't really a knock against the value of the C++ STL smart-pointers collection as a whole—you can entirely ignore the existence of std::shared_ptr (I do!) and still think C++ STL smart-pointers are the cat's pyjamas.
Still, it's just too many layers of complexity for my taste. Nailing performance problems in C is a Sunday walk in the park in comparison.
Live and let live, I was just sharing my experience of going back and forth.
Imagine if C had a solid AST-based macros system...
Because of operator overloading, I don't know if `A * B` is incurring a (nontrivial, arbitrary) function call. Maybe it's just normal scalar multiplication, maybe it's finding the dot product of two arbitrarily sized matrices.
As an example, if I give you this line of C++ code, and no other information (which would require further effort to determine, e.g. the types of identifiers), can you tell me if it's a function call, performs I/O, or does dynamic memory allocation?
auto C = A + B;typeof(A) C = add(A, B)
Now tell me if it involves a function call, performs I/O, or does dynamic memory allocation?
And, of course, things like macros and typeof are totally used in kernels, virtual machines, etc... The ultimate obfuscation is alive & well in those areas. So no, the potential to do terrible things in operator overloading is not even remotely a concern there. No more than anything else.
I can tell you, if I write this in C, it performs no I/O, memory allocation etc:
typeof(A) C = A + B;
I know for a fact that (modulo people doing incredibly stupid things with macros), that this is simple arithmetic addition. I don't know that for a fact in C++ - because of operator overloading.The only way to know for a fact what happens in either C or C++ is to know the types of A & B, which you (seemingly intentionally) omitted.
But if A & B are the types that can be added in C, then it's going to be literally identical in C++ with no chance of operator overloading. If A & B are not types that can be added in C, then you would see an add() function called instead and you'd be right back to not knowing what it does.
We're violently agreeing at this point. Yes, in C, this problem does not exist, because operator overloading does not exist.
In C++, what `A+B` does is ambiguous without knowing 1) what are the types of A and B, and 2) what are all the possible `operator+` overloads that exist which may operate on A and B. It's a hidden complexity which doesn't make potentially expensive operations apparent, and kernels and VMs hate that kind of thing.
Yes, it is. Extremely so.
> Yes, in C, this problem does not exist, because operator overloading does not exist.
Except it totally still does because you never know what function add does for random types. It's literally the same exact thing. Function calls might be expensive and they might not be.
> It's a hidden complexity which doesn't make potentially expensive operations apparent, and kernels and VMs hate that kind of thing.
No, it really isn't. The add overload operator is never non-obvious, and never actually does memory allocation, file io, or any of that other nonsense.
Can you be intentionally stupid about it? Yes, sure. Is that an actual concern that anyone working on a VM or kernel should have? No, not in the slightest. Completely nonsense.