Perhaps the message is aimed at less experienced C++ programmers?
It seems like an article aimed neither at beginners nor experts.
To avoid tons of unnecessary copies use move semantics, which std::optional of course supports.
And while the fabled 'zero cost abstraction' often is able to clean up the mess (at the cost of high build times), a small and innocent looking change in the surface code may throw things out of whack again and suddenly you have an unexpected performance regression.
- If certain operators were being overloaded
- How memory was being allocated in different sections, libraries, etc.
- The different semantics of the various ways you can do the same thing (e.g., constructors)
. . . the list goes on and on and on.
I’m not trying to crap on C++, but for a low-level language, the simplicity of C is simply unmatched!
C is not a simple language. You're either naive or lying to yourself if you think it is.
But in this case C isn't even any different, so this tangent is irrelevant anyway. If you take a struct by value in C it, drum roll, copies it!
The problem with C is that it so simple that it is simple to complain about it.
Other languages hide their quirks deeper down and are harder to complain about.
You’re absolutely correct, but I also don’t have to worry about copy versus move semantics and all that other jazz in C — the mental model is simply value copies or pointer copies, that’s it.
C is a simple language the same way my car is simple if I take the engine out.
C is easily the least defensible language choice of the commonly used languages. There's so many just obscenely better options it's not even funny. See also why even "intentionally C-like" languages like Zig are growing increasing complexity https://github.com/ziglang/zig/issues/7769 Or why things like D's betterC adds all the same "objectionable" stuff C++ does - RAII, operator overloading, etc...
C++ is a minefield. So is C though. Comparatively they're pretty much in the same boat for me. Sorry I guess?
The truth is that you can't explore a C++ codebase without actually stepping through it with the debugger. You literally can't see all the places where you end up. A mechanism to do the type resolution is needed, and that is a much, much bigger mental load.
In C you can see each and every jump by default (unless someone does terrible hidden macro magic etc). Maybe add a code browser for convenience. You can often get a pretty good idea of a codebase just by browsing it. These are definitely diferent qualities.
In comparison to C++ it is. C++ isn't a strict superset of C, but for all intents and purposes, it supports all the nasty warts of C PLUS a million other foot guns added on with no guidance whatsoever on which features are "safe" and "modern".
Just a short list of additional complexities in C++: constructors/destructors, operator overloading, templates, constexpr/consteval, copy constructors/move constructors/perfect forwarding etc, smart pointers (when do you use smart versus raw? Herb Stutter has some guidelines on this), lambdas, massive std lib, etc.
> But in this case C isn't even any different, so this tangent is irrelevant anyway.
But C is different... if you pass by value in C what's going to happen? It copies the bits. What about C++? Drum roll please... Who the heck knows. It could call some copy constructor, or it could just copy the bits, or it could recursively call copy constructors on all the nested objects etc. You literally cannot tell in C++ without looking at the implementation or already knowing what's going to happen.
But hey, it's apparently more important to know it's a memcpy than to know it works! Such simplicity! I'm so glad a built-in operator has just no expectations of being useful or correct, what a truly wonderful language.
> C is not a simple language
In response to the claim that:
> I’m not trying to crap on C++, but for a low-level language, the simplicity of C is simply unmatched!
C is simpler than C++, for the reasons I outlined above. This has no correlation on how correct the code is or if it works. If you want to argue that, go ahead but that's a different argument.
> But hey, it's apparently more important to know it's a memcpy than to know it works! Such simplicity! I'm so glad a built-in operator has just no expectations of being useful or correct, what a truly wonderful language.
Great, you can be facetious all you want, it doesn't add or detract to your claim that C is not simple in comparison to C++.
And by the way, I use C++ and prefer it to C. I love the fact that you can use templates in C++ for type safety when it comes to generic containers and use destructors to help with resource management etc. But let's not kid ourselves, C is simpler than C++ and trying to argue against that is pointless.
C is not simple to use. It's full of non-obvious gotchas with little to no tooling support to aid with handling the vast complexity that the problem space actually contains. To such an extent that, yes, I think the end result is using C correctly is more complex than using C++ correctly despite the C language spec itself being "simpler" - because that simplicity came by punting all the complexity onto the user.
> C is not simple to use. It's full of non-obvious gotchas with little to no tooling support to aid with handling the vast complexity that the problem space actually contains
Once again, C++ contains the same non-obvious gotchas plus several additional gotchas as I said initially. C++ can only ever be as simple as C, not simpler since it aims to be mostly a superset of C. Sure string handling and stuff is annoying in C, but you can create libraries for that stuff the same way C++ has. I'm not sure how else to put this.
Simplicity does not mean ease of use, and the fact of the matter is C will always be simpler than C++ because C++ aims to be a superset of C.
Adding complexity to a language can reduce the complexity in using it. This is the case for C++, Rust, D's betterC, etc... when compared to C. All of them add complexity to the language to reduce the complexity in using it.
If you can't understand this, so be it. But the entire computing industry fully disagrees with you here.
As an aside, your insistence that C++ is a superset of C is factually wrong regardless. C++ changes a number of C behaviors, it is not a strict superset.
Not quite, in C you know that the most that can happen is a memcpy() call (or some form of inlined byte/word copy) whereas in C++ a copy constructor can do anything - like allocating memory, which might fail and raise an exception.
C++ is a strict improvement in that you can at least generally assume the assignment operator produces functional code if it compiles, and types with unusually expensive assignments can opt to prevent the assignment operator from being used at all to make that clear.
How about no? RAII and exceptions do a lot of stuff that is hard to see (because most of the time, there literally isn't any written code that calls them).
RAII code wont be invoked unless you literally declare an object in the function.
You might as well complain that C implicitly reserves space on the stack for variables despite the fact that you did not explicitly call alloca to do it.
alloca() vs stack variables is something different, it's dynamic vs static allocation. You can literally see (ignoring optimization to make this point) all the stack variables that are part of the stack frame. It's in plain sight, very explicit and obvious to the eye. While calls to operator overloads and ctors/dtors are not visible in the plain syntax (type resolution is needed), and in case of virtual methods they might not even be resolved until runtime.
So please don't make me angry, this was a really weak refutal. Statements like this are why I am sometimes more critical at C++ than it maybe deserves.
The fun part is that you can still use plain old objects if you really really want to deal with C-Strings.
> It's in plain sight, very explicit and obvious to the eye.
And since RAII applies to variables the same is true in c++, unless you pretend to write C.
> and in case of virtual methods they might not even be resolved until runtime.
Except the type of a stack allocated object is known at compile time and ctors and dtors are part of that type. For operators you can also generally see from the type if they are overloaded and possibly virtual even if you are not dealing with directly stack allocated objects.
> The fun part is that you can still use plain old objects if you really really want to deal with C-Strings.
It's easy to see you've never seriously programmed C, because strings are like 0.1% of my concerns. They just work where they have to, and otherwise string manipulation is 99% done in places and where a C programmer wouldn't even do it.
> The fun part is that you can still use plain old objects if you really really want to deal with C-Strings.
yawn... But how exactly do I know that anything is a POD structure? And if everything is, so I can assume that anything is, where's all the advantages gone?
If you feel like it is too much effort you can just not do it? I can't remember the last time I checked, ctors and dtors should do basic setup and teardown. Counterquestion, how do you know a specific C struct wont overflow your stack without looking? We are assuming that everything important is visible inline, right?
> Strings are just buffers like any other, nothing special. It's easy to see you've never seriously programmed C
I have seen way too many hand rolled parsers on C, they may have been only 0.1% of the total code but they amounted for at least 50% of the bugs, along with the constant manual memory management and badly initialized variables.
> If string manipulation in C becomes annoying, you should have written a Python script.
That is an interesting statement to make for someone who claims to almost never write C string handling code. As someone who has to deal with strings, std::string is simply supperior in most cases so I prefer to use C++ where I have the choice.
> where's all the advantages gone?
That was in response to the "locked into a framework" claim.
It's all about creating the right abstractions. A parser is not about string handling! The typical (lexical) parser scans input from left to right and builds up (identifier and string literal) tokens by appending. So basically, you need a function input_read_char() and string_push_char(). You're done, there is no other string indexing or string allocation in a parser.
> That is an interesting statement to make for someone who claims to almost never write C string handling code
What I want to say is that if you're dealing with a lot of strings, you're working in a high-level glue domain. And there is no point in using either C or C++. You're probably making a web API, or an ad-hoc throwaway thing. It need not be python, possibly it should even be a shell or awk script. I write lots of those, typically they live and die in my command-line history, or as a 5-line script in my /tmp folder. Using C++ for these tasks would slow me down by 10x.
If your function is currently taking a parameter by-value, and you replace it with std::optional<T>, you're still taking it by value and doing the exact same amount of copies. No (new) issues from optional.
You only run into trouble if you do something like change `void foo(const T&)` to 'void foo(std::optional<T>)`. But in such a change, it's "clear" (insofar as C++ is ever clear about anything) you're replacing a reference parameter with a value parameter.
I don't think this is surprising std::optional behavior?
> The (typical C++ stdlib) problem here is that things that look deceptively simple on the surface may result in a surprising amount of activity under the hood
It's literally exactly what it looks like. The C++ stdlib didn't complicate or obfuscate anything.
In these contexts, the "innocent looking change" is usually something which pushes a method over an inlining threshold, so that something which used to be inlined no longer is, and a zero cost abstraction suddenly has a cost larger than zero.
A pointer is already an optional type by the 0 convention.
But unfortunately also optional references was never resolved & thus punted ( see https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n35... ), even though boost::optional supported them
Most problems don't need that level of reasoning, but when it matters C++ gives you a lot of power to ensure you can control when it happens.
If you do care and know about the particular implementation, I guess you might counter by saying that a COW string would be cheap(ish) to copy, or ditto if the string is short and the implementation uses the small string optimization. But at a higher level it's a very good general rule to just avoid copying anything unless you know for sure that it's small and doesn't use dynamic allocation.
I'm not sure why the author would have ever expected anything different?