If you want to work in C with general-purpose data structures, algorithms, etc. then casting just comes with the territory. Most of the time it can be implicit, but it has to be explicit when you aren't using a void*
pointer to be passed or assigned to anything, e.g.
With C you're working with a type system so simple that allows you to x-ray it and work at the level of bits and bytes without worrying about copy ctors and dtors and so forth. In C++ functions like memcpy
and memset
are evil, horrid functions because they bulldoze over the rich features of the C++ type system by operating with bits and bytes in a language not intended to be used so much for such a purpose. In C, these are right-arm functions to often be used on a daily basis.
C revolves around operating on homogeneous collections of bits and bytes. Data types are just a way to simplify working with bits and bytes, specifying what bits and bytes are what in a given context and how they are aligned. In many cases in C, the same bits and bytes could represent one data type at one moment and another the next. It's just slightly above assembly in that regard. In assembly code, there are no data types. In C, there are data types but they're often transient concepts just to aid the programmer in working with bits and bytes and making sure they invoke the appropriate machine instructions on those bits and bytes.
That often means that as you translate to/from raw bits and bytes, you have to specify what type of data you want the bits and bytes to be treated as. That's the price you pay for being able to so easily work at the level of bits and bytes and not data types. If you want to debug such C code, you also have to get used to explicitly specifying what data type you want bits and bytes to be interpreted as in the debugger. It's not convenient unless you're writing code that benefits from working with raw bits and bytes.
I would argue that C++ is a much, much, much higher level language because of just a handful of features it originally added on top of C, and those would be virtual functions, destructors, and constructors. Those 3 features change the entire nature of the language to a point where you can no longer assume that an arbitrary type, T
, can merely be treated as a homogeneous collection of bits and bytes to be, say, copied around with memcpy
elsewhere or initialized with memset
to set its bits to zero. You can't apply "polymorphism" at the bits and bytes level anymore. It now means that you must always write such generic code against the assumption that you will need to call functions specific to T
to do these types of things. The idea of a data type is no longer something transient or something that can be ignored in specific contexts, and that alone leads to a completely different philosophy and language suitable for very different purposes, and all because of what it added, not what it changed or removed.
C For Data Structures and Allocators
When you need to write code like your own proprietary data structures and memory allocators, then C becomes more convenient than C++. Try writing a fully standard-compliant deque
in C++ with perfect exception-safety, placement new and manual dtor invocation in all appropriate areas, std::allocator
support, forward and reverse iteration, both read-only and mutable iterators, range ctor, fill ctor, initializer list support, with ctors having all appropriate overloads for when the user specifies a custom allocator and when they don't, erase, range erase, insert, range insert, push front, push back, two overloads of operator[] (read-only and mutable), two overloads of front back element access, template specializations for PODs which can use POD-related optimizations (ex: no-throw assumptions), etc. It's definitely not easy, especially the exception-safety part. With C you can just focus entirely on the data structure as an arrangement of bits and bytes which can give you so much time to focus on cache-friendly memory layouts and the algorithm itself. It's so much easier even if the end result is harder to use.
In my case a nice balance for me is using C when I need to implement proprietary data structures, when nothing in the C++ standard library suits the purpose (which is actually quite common for me since the C++ standard library doesn't provide very suitable data structures for, say, commercial-quality raytracing or entity-component systems, and you're not going to get a competitive raytracer by just using someone else's). Then for the mid and high-level code which benefits from just using existing data structures, I use C++. And for the highest-level user-end code which needs very rapid iterations and turnarounds, I use embedded Lua scripts and have been exploring the idea of compiling and dynamically linking C and C++ code on the fly while the engine is running.
I fell in love for a time with writing general-purpose data structures in C++ (actually doing all the work mentioned above for full standard-compliance) and thought C++ was the ideal language for designing and implementing very competitive proprietary data structures because of how much rich feature support you get the moment you provide, say, standard-compliant iterators for your container, and the cost-free abstractions with static polymorphism. I fell out of love with it years later and came full circle back to C, which I now believe is the dream language for people who want to implement the most performance-critical data structures and allocators while still being able to get on with their lives and work towards higher-level design aspects of their product. C++ is ideal for using existing data structures and allocators which other people already put a great deal of effort to develop towards full-compliance; you can reap a great deal of benefits provided they spend that enormous time on their data structure. But if you want to get on with your life while still being required to write competitive new implementations of data structures with code that builds in a fraction of a second, then I think C is a much more productive fit but again, it does mean you will have your share of casting here and there (implicit or explicit).