Not really my style, though admittedly I’m not a big C++ fan. It does generalize exactly this certain set of cases, but what happens if for example you add a mode that could operate on multiple pixels per loop?
The problem with C++ is not the features that help you write better, safer code as explained here. The problem is that C++ has so much bad and people do use it. C++ is good as long as it's a solo project, it's okay for small projects, it inevitably fails on large projects.
C has gotten a lot better through standardization and tooling, it really is vastly improved compared to the flawed experience that K&R describes.
I can't say the same about C++, which feels as if one has to set clear and definite limits, banning most of the language, before one can deploy it in a sane manner.
C++ has so much language bloat I avoid it for that reason alone as working with other people's C++ code is such a pain (most of the time, not always). C is more verbose in some ways but doesn't suffer all that much from bloat, which makes it more appealing, to me anyway.
C++ has a lot of features (large surface area) and power. This power is the result of powerful language constructs like inheritance, operator overloading, templates, etc. This can make it very powerful and concise for writing code. When power is hidden, it makes it very difficult to read.
C just has data structures and functions (small surface area). Its simplicity makes it much easier to read.
Some languages prioritize adding features and power. C++, Rust, and Swift are examples.
Other languages prioritize minimizing the surface area. C, Go and Scheme are examples.
Programmers either value minimal surface area (better for reading) or maximal features and power (better for writing) and are more likely to adopt languages based on this classification.
For example, C programmers are more likely to adopt Go over C++ and Rust. And, C++ programmers are more likely to adopt Rust.
This summary is absolutely amazing. It makes you think, given that code gets read a lot more than it gets written. It also makes me curious about Scheme, because I didn't know that it belongs to that category. I think that it's also important to add that C++ sort of belongs to a category of its own, because it has an absolutely enourmous surface area compared to e.g. Rust.
I actually disagree, while Rust has a great deal of features it’s still significantly easier to read. A big part of that I think comes from not having implicit function overloads and having not succumbed to the multitude of bikeshed acceptable language subsets C++ has developed over the many years it has existed. The rust you write on a microcontroller isn’t actually that different than the rust you’d write for a web app. Perhaps a clear sign that the parents statement is evidently incorrect is the acceptance of rust and the utter denial of C++ in the Linux kernel, where developers demand readability. Part of that may very well be what Rust provides as a feature set is enticing enough to work with whatever readability issues have come up.
In embedded systems, space can be a premium (e.g. a recent project has 75MB for a rootfs). With C++, need to link to libstdc++ and libc, with C only need libc.
I find a number of developers reach for C++ because they aren’t comfortable with C or low-level development. For instance, will copy memory into a std::vector or std::string just to use the STLs search functions where a simple finite state machine would be more appropriate. I also find a correlation between C developers having a better understanding of things like file descriptors, poll/select/epoll for an event loop, while a C++ developer is more likely to use std::thread and then have to deal with proper locking.
Working in a constrained environment, I like C exactly because it “does less”. It’s more explicit, simpler, hides less behind abstractions in practice.
>For instance, will copy memory into a std::vector or std::string just to use the STLs search functions where a simple finite state machine would be more appropriate.
The STL was specifically designed so that pointers are iterators and you are able to pass a pair of pointers to pretty much any STL algorithm. This you should never need to copy memory into a container such a vector or string to be able to use the STL algorithms.
The right way to do this in C would be to define each type of pixel in its own file, along with processing functions, and use function pointers within the structs for required processing.
C++ classes are really just syntactic sugar for data organization. You can do the same type of organization in C, things just have to be more explicit. The benefit if using C is that you can use constructs where needed while trimming fat where its not.
Nobody should do that. We use a temporary variable of the correct type. For example this way (but it could also be done with pointers to access directly the union variable):
switch(mode) {
case AMBIENT: {
PixelDataAmbient l = left.a;
PixelDataAmbient r = right.a;
int steps = r.x - l.x;
float dv = (r.ambientLight - l.ambientLight)/steps;
/* ... */
break;
}
case GOURAUD: {
PixelDataGouraud l = left.g;
PixelDataGouraud r = right.g;
int steps = r.x - l.x;
/* ... */
break;
}
/* ... */
default:
show_error("Illegal mode");
}
There is no need to cart all the .a. and .g. around and clutter the code. And there is no possibility to accidentally access the wrong fields the wrong way: trying to access l.ambientLight in the second block (GOURAUD) will fail at compilation since a PixelDataGouraud doesn't have an ambientLight field. It is also not possible to do PixelDataGouraud l = left.a; since left.a is not a PixelDataGouraud.
Much cleaner, much more readable and avoids most of the possible errors he complains about.
You can definitely do that, however in practice, separating out by files makes the code very easy to test, since you can validate functionality per pixel type, and then validate your outer loop by passing fake objects with correct functions.
You can also have internal workings with static functions/variables that you can use as helpers, but invisible to outside code and not declared in header files.
17 comments
[ 3.5 ms ] story [ 51.3 ms ] threadThis is not even REMOTELY true. Plenty of companies use massive pure C++ projects.
I can't say the same about C++, which feels as if one has to set clear and definite limits, banning most of the language, before one can deploy it in a sane manner.
C just has data structures and functions (small surface area). Its simplicity makes it much easier to read.
Some languages prioritize adding features and power. C++, Rust, and Swift are examples.
Other languages prioritize minimizing the surface area. C, Go and Scheme are examples.
Programmers either value minimal surface area (better for reading) or maximal features and power (better for writing) and are more likely to adopt languages based on this classification.
For example, C programmers are more likely to adopt Go over C++ and Rust. And, C++ programmers are more likely to adopt Rust.
I find a number of developers reach for C++ because they aren’t comfortable with C or low-level development. For instance, will copy memory into a std::vector or std::string just to use the STLs search functions where a simple finite state machine would be more appropriate. I also find a correlation between C developers having a better understanding of things like file descriptors, poll/select/epoll for an event loop, while a C++ developer is more likely to use std::thread and then have to deal with proper locking.
Working in a constrained environment, I like C exactly because it “does less”. It’s more explicit, simpler, hides less behind abstractions in practice.
The STL was specifically designed so that pointers are iterators and you are able to pass a pair of pointers to pretty much any STL algorithm. This you should never need to copy memory into a container such a vector or string to be able to use the STL algorithms.
C++ classes are really just syntactic sugar for data organization. You can do the same type of organization in C, things just have to be more explicit. The benefit if using C is that you can use constructs where needed while trimming fat where its not.
Here's what he wrote in order to complain about it:
Nobody should do that. We use a temporary variable of the correct type. For example this way (but it could also be done with pointers to access directly the union variable): There is no need to cart all the .a. and .g. around and clutter the code. And there is no possibility to accidentally access the wrong fields the wrong way: trying to access l.ambientLight in the second block (GOURAUD) will fail at compilation since a PixelDataGouraud doesn't have an ambientLight field. It is also not possible to do PixelDataGouraud l = left.a; since left.a is not a PixelDataGouraud.Much cleaner, much more readable and avoids most of the possible errors he complains about.
You can also have internal workings with static functions/variables that you can use as helpers, but invisible to outside code and not declared in header files.