开发者

How to elegantly implement a series of functions in different type versions using pure C?

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 though, few compilers support export keyword and this keyword is queried for efficiency). For easy example, I want:

template <typename T>
T add(T a, T b){
    return a+b;
}

However, in pure C (sometimes I have to choose pure C, as some platform doesn't have the C++ compiler), there have to be different function names for different versions, as

double addDouble(double a, double b){
    return a+b;
}

int a开发者_如何转开发ddInt(int a, int b){
    return a+b;
}

Hmmm, when there are two versions, it seems OK that I can do the copy-and-paste work in a source file; However, in practice there would be many lines instead of just a return statement in a function and would be more versions. So, my question is, how to implement a series of functions in different type versions elegantly?

So far I have tried some solutions as below, but I think they are far from good. I need your suggestions, thank you!

Solution 1:

#define mytype int
mytype addInt(mytype a, mytype b){
    return a+b;
}
#undef mytype

#define mytype float
mytype addFloat(mytype a, mytype b){
    return a+b;
}
#undef mytype

Shortcoming of Solution 1: duplicated contents are too many, and if I want to modify the function, I have to modify all of versions.

Solution 2:

func.h

#ifndef FUNC_H
#define FUNC_H

#define add(a, b, typename) functionAdd##typename(a,b)

/* function declarations */
#define declared(typename) \
typename functionAdd##typename(typename, typename)

declared(int);
declared(float);

#endif

func.c

#include "func.h"

/* function code */
#define functionAdd(a, b, typename) \
typename functionAdd##typename(typename a, typename b){ \
    return a+b; \
}

/* function bodies (definitions) */
functionAdd(a, b, int)
functionAdd(a, b, float)

main.c

#include <stdio.h>
#include "func.h"

int main()
{
    int x1 = add(1, 2, int);
    float x2 = add(3.0, 4.0, float);
    printf("%d %f\n", x1, x2);  
    return 0;
}

Shortcoming of Solution 2: Because the function is written in define, it's difficult to debug. Besides, the \ notation is annoying. Though, it's convenient to add a new version, just inserting declared(double) into func.h and functionAdd(a, b, double) into func.c will achieve this aim.


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

  1. 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);
    
  2. 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 would also propose Solution 4: write a code generation tool.

Pros:

  • result is a clean debuggable code;
  • unlimited configurability to your needs (if you have time of course);
  • long-term investment to a dev's toolset.

Cons:

  • takes some time, esp. at start, not always sutable for write-once code;
  • complicates build process a bit.


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.


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;
}


I don't think you can do much better than your solution 2 in pure C.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜