I'm going to start this from the perspective of C++, then work my way into C.
In statically-typed languages like C, C++, Java, etc., a "generic" function allows you to specify the function operations once, using placeholders for any types that may vary between different calls (which means functions like qsort
and bsearch
are most definitely not generic functions). Ideally, you'd also like the compiler to automagically detect any calls to this generic function and generate the actual code as necessary.
C++ makes this easy1 by offering templates:
template <typename T>
T summation( T *values, size_t numValues )
{
T result = 0;
for ( size_t i = 0; i < numValues; i++ )
result += values[i];
return result;
}
T
is a placeholder for any type2, so you can call it as
int ivals[] = {1,2,3,4,5,6,7,8,9};
double dvals[] = {1,2,3,4,5,6,7,8,9};
int sumi = summation( ivals, 10 );
double sumd = summation( dvals, 10 );
When the code is compiled, the compiler sees the two calls to summation
and deduces the types of the arguments. For each different type, it generates a new instance of the function, giving it a unique name:
int summation_i( int *values, size_t numValues ) // actual compilers will generate
{ // more complex "mangled" names
int result = 0; // than this
...
}
double summation_d( double *values, size_t numValues )
{
double result = 0;
...
}
It then generates code such that the result of summation_i
is assigned to sumi
and summation_d
is assigned to sumd
.
C doesn't offer anything similar to the template feature. Traditionally, we've attacked generic programming in one of two ways - either by using macros or by using void *
everywhere and delegating type-aware operations to other functions.
Here's a bad example of a macro-based solution:
#include <stdio.h>
#define SUMMATION_DEF(t) \
t summation_##t( t *values, size_t numValues ) \
{ \
t result = 0; \
for ( size_t i = 0; i < numValues; i++ ) \
result += values[i]; \
return result; \
}
#define SUMMATION(t,x,s) summation_##t(x, s)
SUMMATION_DEF(int)
SUMMATION_DEF(double)
int main( void )
{
int ivals[] = {1, 2, 3, 4, 5};
double dvals[] = {1, 2, 3, 4, 5};
int sumi = SUMMATION(int, ivals, 5);
double sumd = SUMMATION(double, dvals, 5);
printf( "sumi = %d\n", sumi );
printf( "sumd = %f\n", sumd );
return 0;
}
The SUMMATION_DEF
is roughly similar to a template in that it specifies the function operations, using the macro parameter t
as a type placeholder. We also use t
as part of the function name - the ##
is the token pasting operator, and the preprocessor will expand t
and append that value to the name of the function3.
Where it differs from C++ is the fact that a macro is just a dumb text substitution. It doesn't trigger any special operations on the part of the compiler. The actual function instances aren't automatically generated based on any invocations of the SUMMATION
macro - we have to explicitly generate the functions we want (hence the SUMMATION_DEF(int)
and SUMMATION_DEF(double)
before main
). It also means that when we call summation_xxx
through the SUMMATION
macro, we have to pass the type as part of the macro argument list, such that the right function gets called. What a pain.
The C 2011 standard added the _Generic
keyword, which can make life a little easier in that respect:
#include <stdio.h>
#define SUMMATION_DEF(t) \
t summation_##t( t *values, size_t numValues ) \
{ \
t result = 0; \
for ( size_t i = 0; i < numValues; i++ ) \
result += values[i]; \
return result; \
}
#define SUMMATION(x,s) _Generic((x), \
int * : summation_int, \
double * : summation_double \
)(x, s)
SUMMATION_DEF(int)
SUMMATION_DEF(double)
int main( void )
{
int ivals[] = {1, 2, 3, 4, 5};
double dvals[] = {1, 2, 3, 4, 5};
int sumi = SUMMATION(ivals, 5);
double sumd = SUMMATION(dvals, 5);
printf( "sumi = %d\n", sumi );
printf( "sumd = %f\n", sumd );
return 0;
}
The _Generic
keyword allows you to evaluate expressions based on types; thus, if the type of the first argument to SUMMATION
is int *
, we call summation_int
; it's it's double *
, we call summation_double
. This way we don't have to specify the type name in the macro arguments.
The other approach, as you've seen, is to use void *
and to delegate type-aware operations to other functions. Like I said above, that's not really "generic" programming, since you have to manually implement each comparison function for each type. You can't just code it once and be done with it. And by using void *
, you basically throw type safety out the window and into oncoming traffic.
And before anyone complains - no, none of these summation functions check for or deal with arithmetic overflow. That's a subject for another day.
- For sufficiently loose definitions of "easy". The metaprogramming language used to support templates is Turing-complete, so you can do *really amazing* and impossible to understand things with it.
- For sufficiently loose definitions of "any type". Note that whatever type you use must support the
+=
operator, otherwise the compiler will yell at you.
- This code will break for types like
unsigned int
or long double
since they have whitespace in the name. I don't immediately know the solution to that problem, and I've spent enough time on this answer as it is.