I want to write several functions that are only different in the types of arguments. I know C++ has template
to handle this problem well (not very well yet thou
If you think using the C preprocessor is awkward and hard to debug, how about writing a script in some more convenient language to generate a .c
file you can #include
? Most modern scripting languages come with some sort of template engine, but since your requirements are simple enough, it doesn't have to be any more complex than this;
#/bin/sh
for t in int double char mytype; do
cat <<____HERE
$t add_$t ($t a, $t b) {
return (a + b);
}
____HERE
done >generated.c
The resulting file will be plain-jane straightforward C which should be reasonably simple to debug and change.
I would also propose Solution 4: write a code generation tool.
Pros:
Cons:
In many (if not most) cases the best way to simulate C++ templates in C would be Solution 3: parametrized header file and parametrized implementation file. In your case it would work as follows
Create a meta-header file, which we'll name add.dec
, that looks as follows
TYPE_ CONCAT(add, SUFFIX_)(TYPE_ a, TYPE_ b);
TYPE_ CONCAT(sub, SUFFIX_)(TYPE_ a, TYPE_ b);
Create a meta-implementation file, which we'll name add.def
, that looks as follows
TYPE_ CONCAT(add, SUFFIX_)(TYPE_ a, TYPE_ b){
return a + b;
}
TYPE_ CONCAT(sub, SUFFIX_)(TYPE_ a, TYPE_ b){
return a - b;
}
These two files are parametrized by two macros: TYPE_
and SUFFIX_
, while CONCAT
is a traditional implementation of macro concatenation
#define CONCAT_(a, b) a##b
#define CONCAT(a, b) CONCAT_(a, b)
Now, imagine you want to instantiate your "template" functions for types int
and double
. In a "real" header file add.h
you simply do
#define TYPE_ int
#define SUFFIX_ Int
#include "add.dec"
#undef TYPE_
#undef SUFFIX_
#define TYPE_ double
#define SUFFIX_ Double
#include "add.dec"
#undef TYPE_
#undef SUFFIX_
and in a "real" implementation file add.c
you do
#define TYPE_ int
#define SUFFIX_ Int
#include "add.def"
#undef TYPE_
#undef SUFFIX_
#define TYPE_ double
#define SUFFIX_ Double
#include "add.def"
#undef TYPE_
#undef SUFFIX_
That's it. By doing this you instantiated (declared and defined) addInt
, addDouble
, subInt
and subDouble
.
Of course, you can parametrize the declarations much more. You can add a DECLSPEC_
parameter to be able to declare your sunctions as static
, if necessary. You can specify different types for parameters and return values (say, ARG_TYPE_
and RET_TYPE_
). You can parametrize lots of other things. Basically, there's no limit to what you can parametrize. With some fairly easy macro techniques you can even parametrize the number of parameters your functions expect.
This is actually similar to your Solution 1 and Solution 2 combined. This basically takes the best from both of your approaches. And I'd say that this is the most faithful attempt to simulate the behavior of C++ template instantiation.
Note that each function's body is explicitly typed only once (as opposed to multiple explicit copies in your Solution 1). The actual function bodies are also easily editable, since there's no need to worry about those pesky \
at the end of each line (as is the case in your Solution 2).
This approach has another interesting benefit: the code in add.def
will remain "debuggable", i.e. an ordinary interactive debugger will typically be able to step into these implementations (which is impossible in your Solution 2).
I don't think you can do much better than your solution 2 in pure C.
You can use union:
#include <stdio.h>
#include <stdarg.h>
typedef enum {Int, Double} NumberType;
typedef struct {
NumberType type;
union {
int i;
double d;
};
} Number;
Number addNumber(Number a, Number b) {
Number ret;
Number *numbers[] = {&a, &b};
if (a.type == Int && b.type == Int ){
ret.type = Int;
ret.i = a.i + b.i;
}
else {
ret.type = Double;
char i;
for (i = 0; i < 2 && numbers[i]->type == Int; i++) {
numbers[0]->d = (double) numbers[i]->i;
}
ret.d = a.d + b.d;
}
return ret;
}
Number newNumber(NumberType type, ...) {
va_start(ap, type);
Number num;
num.type = type;
switch (type) {
case Int: {
num.i = va_arg(ap, int);
break;
}
case Double: {
num.d = va_arg(ap, double);
break;
}
default: { /* error */
num.type = -1;
}
}
va_end(ap);
return num;
}
int main(void) {
Number a = newNumber(Int, 1);
Number b = newNumber(Double, 3.0);
Number ret = addNumber(a, b);
switch (ret.type) {
case Int: {
printf("%d\n", ret.i);
}
case Double: {
printf("%f\n", ret.d);
}
}
return 0;
}