Complaining about a language having features you don't want is silly. C++ doesn't take longer to compile if you don't abuse templates.
Complaining about a language having features you don't want is silly. C++ doesn't take longer to compile if you don't abuse templates.
It might be silly if you're working on your own. Software that delivers a lot of value is usually developed and evolved not only by team, but by a team with changing members and changing leadership over the project's lifetime. The features used will be the union of all features used over the years, and while it's easy for team leads to allow the use of more features than their predecessors, it's quite hard to reduce them.
Also, you may be forced to use language features you don't want if they're used by libraries whose functionality you do want. For example, when doing low-level programming, I don't like implicit calls that I can't clearly see on the page (e.g. destructors or overloaded operators). But if libraries I want use them, then I'll have those implicit calls. But if the language doesn't have those features, libraries obviously won't use them.
That's exactly the case when it's easiest. If you don't need a feature, just don't use it and case closed. With a team it's harder - you have to force/enforce others not to use a given feature.
> if they're used by libraries whose functionality you do want
If you're using C++ you can just use the C library you would've used otherwise, no?
This is quite important and often overlooked. An annoying fallacy is that people think some features are optional, but once they get heavily used, they aren't anymore. Often they quickly become an requirement and if you don't follow suit, your code is legacy or you are an idiot. But well, I guess I create legacy code upfront and the cargo cultists create modern code that turns into legacy next day.
It actually does though, unless you also drop C++ stdlib usage completely (have you looked at how many lines of code just <vector> alone pulls into each source file? - it's upward of 20kloc and growing with each new C++ version).
And at that point you get into discussions with various C++ camps about why you don't use the C++ stdlib and instead prefer to reinvent the wheel (and this friction with other C++ coders is the main problem of carving out your own subset - it works ok in complete isolation, but software development work hardly happens in splendid isolation and even then you'd might to want to use C++ libraries written by other people from time to time...)
And once you've been dragged into such C++-subset-discussion month after month, year after year, at that point it is much less exhausting to just write plain C. And the C community (if it can be called that) seems to be much less concerned about coding style dogma and generally a nicer bunch to interact with.
FWIW, I switched around 2017 and each time I have to interact with a C++ library for lack of alternatives it's usually not a pleasant experience (with the notable exception of Dear ImGui - but even there I started to prefer the C bindings so that I don't need to strictly separate the UI code from the rest of the code base, which sometimes makes sense, but often not, especially with an immediate mode UI framework).
C++ dynamic dispatch (your "virtual interfaces") is achieved by welding a vtable onto every type and providing a pointer to that vtable for instances of the type. If in 90% of your code you deal with specific types like Goose or Swan or Duck or Seagull, and only 10% needs to work with the broad Bird category well, too bad, every Goose, Swan, Duck and Seagull carries around that vtable pointer even if it goes nowhere near that 10% of the system. This way your Bird code "just works" in C++.
That's not the only way to crack this nut. Idiomatic Rust approach uses vtables only in the Bird code, elsewhere they don't exist, and thus don't take up space in a Duck or whatever that's always a Duck, but in exchange now you're spending more time thinking, because by default there aren't any vtables and so dynamic dispatch isn't possible at all.
So while that C programmer has to implement features by hand, they are at least able to specifically implement the feature they wanted, not whatever was easiest for Bjarne Stroustrup last century.
C programs tend to nudge you into thinking in terms of arrays of data.
For game development you generally want to think this way. The cost of vtables and all the cache misses doesn’t have to be paid. A game has to stream bytes. Many things at once. Rarely single elements at a time.
There is this anti-C++ bias that keeps forgetting that using C++ doesn't mean use every feature.
Just like many keep using C as if C89 was latest, and never adopt anything added in the last 30 years.
>> C89
> agree :D
The struggle is that if you choose a subset of the language to use and you have dependencies… well you’re including whatever features they use into your subset.
I don’t think C++ is a bad language at all or anything. Just that it nudges developers into certain modes of thinking based on the features it chooses to focus on. Might explain why some developers still choose C.
It's not true. Virtual methods table is present only for classes with at least one virtual method. Learn C++ properly before doing such claims.
Now with reflection even more tools will be available.
Which is why despite all its warts and security flaws, many inherited from C source code compatibility, many domains will keep using it, because they will complain about their missing 1% that no one else uses.
You can write C++ style OOP hierarchy code in Rust but that's not idiomatic, and you can write Rust style explicit dynamic dispatch in C++ but again it isn't idiomatic.
You can get exactly what you are asking for in C++ using techniques of static polymorphism and CRTP pattern (https://en.wikipedia.org/wiki/Curiously_recurring_template_p... and https://en.wikipedia.org/wiki/Barton%E2%80%93Nackman_trick) along with traits and dynamic dispatch (if needed).
For great examples of the above, see the classic Scientific and Engineering C++: An Introduction with Advanced Techniques and Examples by Barton & Nackman (1994).
What i am pointing out are neither complex nor any magic under-the-hood. They are simply techniques in the C++ repertoire well-known since the early 90's and used in all high-performance C++ libraries.
tialaramex made a big deal out of overheads in C++ dynamic dispatch (which incidentally are pretty minimal) using a trivial example, when performance focused (both time and size) C++ programmers do not use it that way at all. Modeling in C++ can be done in any number of ways and is driven by specific perspectives of the end goal.
Surprisingly, this is not true. I've written a C++ file only to realize at the end that I did not use any C++ features. Renaming the file to .c halved the compilation time.
After writing that, I wrote my own standard library (it has data structs like vector, hashmap and sets; slices, strings, rng, print, some io functions, and more) which uses a lot of templates, and it compiles in <200ms on both clang and gcc. Many standard library headers take much longer to compile than that. It's not a terrible idea to have your own standard lib if you need quick compile times.
I have a "mini-std" headerfile that's about 500 LoC implementing lightweight variants of std::vector, std::function, a stack-local std::function (unsafe as hell and useful as hell to avoid allocations), a shared-ptr, qsort and some other nifty stuff.
That does a lot of things, but even then I use other patterns that brings a lot of bang for the buck without having to go full C (hint: the stack-local function equivalent gets a lot of mileage).
On top of likely having worse performance.
int a = 3;
foo(a);
// What value has a ?
There are various things one does not have to worry about when using C instead of C++. But the brain needs some time to get used to it. #define foo(a) a = 12If that were actually true C++ would be a lot easier to accept as a 'C successor'. Instead you have the implicit this-pointer, complicated rules for which constructor or operator overload is actually called, a hidden vtable pointer (not to mention multiple inheritance), then you have public/private/protected, override vs final, const methods (which wouldn't be needed as a separate syntax feature if the this arg wouldn't be implicit) etc etc etc... that a lot of OOP-baggage which a lot of C++ coders probably don't even notice anymore.
A plain C struct with function pointers does indeed make a lot more sense than all the OOP-ism that C++ hides from you ;)
Have you bothered to look how GCC and clang are implemented?
Drivers, and extension points for userspace.
If your criteria for a good language is "how many features does it have", then sure, C++ wins. OTOH, if you criteria is "How many footguns does the language have" then C++ loses to almost every other mainstream language, which includes C.
Sometimes the lack of footguns is a plus.
Sure, but the weighting would be different for different people.
> C may have fewer footguns than C++, but it still has many, whilst also lacking many useful features
We are not talking "2 fewer footguns", or "5 fewer footguns"; we are talking "dozens fewer footguns".
When I need a language with more features than C, I don't choose C++, because the choice is not "Use C for simplicity, and use C++ to trade simplicity off against features", it's usually "Use C for simplicity, and use Java/C#/Go/Rust for features".
The main difference from choosing a different subset, e.g. “Google C++” (i.e. writing C++ according to the Google style guide), is that the compiler enforces that you stick to the subset.
Oh, and smart pointers too.
And hash maps.
Vectors too while we're at it.
I think that's it.
The latter two (hash maps and vectors), though, are just compound data types that can be built on top of standard C. All it would need is to agree on a new common library, more modern than the one designed in the 70s.
Yes, SDS exists, however vocabulary types are quite relevant for adoption at scale.
I think you want the string slice reference type, what C++ called std::string_view and Rust calls &str. This type is just two facts about some text, where it is in memory and how long it is (or equivalently where it ends, storing the length is often in practice slightly faster in real machines so if you're making a new one do that)
In C++ this is maybe non-obvious because it took until 2020 for C++ to get this type - WG21 are crazy, but this is the type you actually want as a fundamental, not an allocating type like std::string.
Alternatively, if you're not yet ready to accept that all text should use UTF-8 encoding, -- and maybe C isn't ready for that yet - you don't want this type you just want byte slice references, Rust's &[u8] or C++ std::span<char>
Hash maps are mostly only important because everyone ought to standardize on a way of hashing keys.
But I suppose they can both be “bring your own”… to me it’s more that these types are so fundamental and so “table stakes” that having one base implementation of them guaranteed by the language’s standard lib is important.
And it wasn't hard to achieve. The idea was to use length delimited strings rather than 0 terminated. This meant that slices of strings being strings is a superpower. No more did one have to constantly allocate memory for a slice, and then keep track of that memory.
Length-delimited also super speeded string manipulation. One no longer had to scan a string to find its length. This is a big deal for memory caching.
Static strings are length delimited too, but also have a 0 at the end, which makes it easy to pass string literals to C functions like printf. And, of course, you can append a 0 to a string anytime.
One of the fun things about Empire is one isn't out to save humanity, but to conquer! Hahahaha.
BTW, one of my friends is using ClodCode to generate an Empire clone by feeding it the manual. Lots of fun!
And const references.
And lambdas.
So that wouldn't really fit C very well and I'd suggest that Rust's String, which is essentially just Vec<u8> plus a promise that this is a UTF-8 encoded string, is closer.
The differences are the usual that occur with guest languages, in this case the origin being UNIX and C at Bell Labs, eventually each platform goes its own merry way and compatibility slowly falls apart with newer versions.
In regards to C89 the main differences are struct and unions naming rules, () means void instead of anything goes, ?: precedent rules, implicit casts scenarios are reduced like from void pointers.
I feel like if you need to implement modern language features, you shouldn't be using C. The entire point of C is to not be modern.
It's arguably irrational to evaluate a language based on this, but you can think of "this code could be better" as a sort of mild distraction. C++ is chock full of this kind of distraction.
A reason I can think of to not move to C++ is that it is a vast language and, if you are working on a team, it can be easy for team members ultimately forcing the whole team to become an expert in C++ simply because they all will be familiar with a different set of C++ features.
But for a solo dev? No reason not to use it, IMO. It's got a much nicer standard library with a rich set of datastructures that just make it easier to write correct code even if you keep a C style for everything.
Being on Borland ecosystem that was followed by several years of Object Pascal (I was already using TP at the time), and C++.
Never got the point of why to keep using C, other than external constraints like school assignments, jobs requirements or lack of compilers.
A lot of smart people pick and choose what they want from the language, just like religion, they keep the good parts and discard the bad.
Even with the recent extension where it looks like it isn't, the compiler adds one for you.
Regrettably not every C++ feature is free if you don't use it. But there aren't many that aren't.
That's definitely harder.
Mindread much?