Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c

coding-style

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 addInt(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.

like image 977
Stan Avatar asked Aug 25 '11 06:08

Stan


People also ask

What is __ Builtin_ffs?

Built-in Function: int __builtin_ffs (int x) Returns one plus the index of the least significant 1-bit of x , or if x is zero, returns zero. Built-in Function: int __builtin_clz (unsigned int x) Returns the number of leading 0-bits in x , starting at the most significant bit position.

What type of functions are existing in C?

There are two types of function in C programming: Standard library functions. User-defined functions.

Can C have functions with same name?

Two or more functions can have the same name but different parameters; such functions are called function overloading in c++.

How do you write a good CPP?

Here are some hints for writing better C++ programs, in no particular order: Write only one statement per line. Limit lines to 80 characters maximum. When using line comments (i.e., comments at the end of a line of code), be sure that the comments on different lines begin in the same column.


1 Answers

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).

like image 84
AnT Avatar answered Oct 13 '22 00:10

AnT