You don't cast your c void* into anything. You use memcpy and function pointers that are passed in. The type agnostic algorithm uses only basic machine types for control flow. That's all it needs. Numerous sorting and searching algorithms and structures can be implemented this way. Think about what your typical <t> algorithm actually needs. Most of these just use storage size and one or two member functions. These can be function parameters instead of type parameters, making the code applicable to different sizes and types at runtime if needed. This makes the C "generic" twice as generic as the C++ template, which is hamstrung to be a compile time generic only. Monomorphism in C ( e.g. calling memcpy with a propagated constant size) is optional depending on how the code is compiled, and it only happens when the function is inlined. C functions can be inlined inside a wrapper that makes certain parameters constant, yielding equal performance to <t> monomorphisms. C++ <t> is always monomorphic once compiled.
By "container" in C I mean a separate retrieval structure. Usually an array. So you have an array of T and an array of U and then have a single monomorphic function handle T for all U. When you start tupling it up in C++ you need a new monomorphism for every combo, essentially duplicating the T code. </t></t></t>
Your perspective seems to be focused on a comparison of void* in C to templates in C++.
You use memcpy and function pointers that are passed in.
Those function ptrs are typed. This is not "generic".
These can be function parameters instead of type parameters, making the code applicable to different sizes and types at runtime if needed.
This is also done in C++, and with a large set of type-safety features that C lacks. Function pointers are not inherently better than functors.
This makes the C "generic" twice as generic as the C++ template
and it confers no benefit, whatever "twice as generic" means here
, which is hamstrung to be a compile time generic only.
C++ virtual tables are not an inherent improvement over C, and modern C++ development eschews vtbls for performance reasons. (It's not hard to implement vtbls in C, but it's kind of ugly, and undesirable.) Not sure why you would want run-time polymorphism in C, to be honest.
Inlining a function in C++ is perfectly fine. constexpr-ing an expression is better. If you can tolerate larger data segments, a constant-expression carries all of the benefits of the C++ type system that C simply does not have. There is no equivalent to this in C, and it provides case-specific optimization limited only by the lexer's expression memory. And that's without templates or preprocessor symbols.
There are optimizations that can be done with C++ templates and template-constraints that easily outstrip even the smartest SSO AST optimizers. The C++ WG did this in part to remove the need for code so highly customized that it begins to operate like a DSL, while still providing best-case performance from a conformant compiler.
I'm specifically talking about generic functions using e.g. (void data, int (do)(...), size_t size, ...). This is NOT pop vtable polymorphism.
Void* gets a bad rap because people abuse it to implement C++ virtual func polymorphism in C. User code has types which it retains on the user side of the generic interface struct foo *f = malloc(sizeof(struct foo))
is actually a generic call made in good C style. malloc doesn't know anything about foo yet is able to allocate a foo because the necessary type info "size" is received as a parameter. Notice the lack of an explicit cast for f
.
In the code style I'm talking about you would never cast a void pointer. You don't know what it is. You don't want to know. Does malloc care what type it returns? No. That's what void* means.
You would also never try to extract a function pointer from a void* since you're not interested in polymorphism. Virtual calls are not in this vocabulary. qsort_r takes a comparison operator int (*compar)(const void *, const void *, void *)
. This is generic not polymorphic (in the C++ style). There is no base class or vtable. qsort doesn't need to query the void* data to see how to compare it. It doesn't care. All it needs to do is count, compare, and swap elements. The parameters reflect this. The single non-void type is int
for control flow.
This style of generic is very good for language interop and systems programming. Why? Because it's not prematurely monomorphized. It can be, but doesn't need to be. That's an advantage.
You use memcpy and function pointers . . . just use storage size and one or two member functions.
Right. Ok. Gotcha.
calling memcpy with a propagated constant size) is optional depending on how the code is compiled, and it only happens when the function is inlined
"... only when inlined . . ." this is only (?) the inline-ing performing the function of parameter passing on the stack. It must be, cos if not, then you're saying that inlined functions operate differently. And that would be fucked up.
When you start tupling it up in C++ you need a new monomorphism for every combo, essentially duplicating the T code.
well, if that's a problem, then that's just an optimization error. But it's not a problem: These are types to start with. And if the three or twenty instantiations of some templated objects/classes and algorithms (int vs my-complicated-thing vs some-externally-manipulated-thing vs another templated thing) require different code, then so be it. Yes, inefficient. Yes not-runtime variable, Yes, C++ has again been shown to be fucked . . .
. . . well, I don't know. I think it's reasonable to have a compiler validate all possible paths, structures. That's kinda the deal with C++, it's completely uptight. What you are suggesting is a liberating and efficient freedom - yay for that - except.... except when you want to be sure (as possible) that a program is going to do what you want it too. Fail early.
I think you are basically saying:
the C method you suggest is more elegant
the C method can handle runtime (compile-time unknown) strucures
C++ templates are compile time only
C++ templates produce lots of code.
Yeah. sure. I agree with all of those. And with C++: what the fuck?
To sum it up: A <T> parameter must always be known at compile time. A function parameter list (T_size, T_compare, ...) doesn't have have this restriction yet can still be optimized when T is known at compile time. By using <T> you've mangled your code to make it less general and less reusable in order to get the compiler to write efficient code. This is the quintessential premature optimization.
There are other uses of templates. Parametric polymorphism is great. But templates actually suck at this too and so does C++'s type system. That's another topic.
(post is archived)