Reid.Atcheson
Reid.Atcheson

Reputation: 151

Generating C code for functions of different signatures, but same implementation

A situation I run into a lot in writing C code (context is scientific computation) is that I will have functions which have exactly the same body modulo minor type differences. I realize C++ offers the template feature and function overloading which allows one to have only one copy of said function and let the compiler figure out what signature you meant to use when you build.

While this is a great feature in C++, my project is in C and I furthermore do not need the full power of templating. So far what I have tried is m4 macros on a candidate source file, and this spits out respective .c files with appropriate name mangling for the different types I need. The preprocessor could therefore accomplish this as well, but I'm attempting to avoid using it in complicated ways (my code needs to be understandable for reproducibility reasons). I'm not very good with m4, so all the files have been hacks that only work in specific cases and are inapplicable in new situations.

What do other people programming in C do when this is necessary? Manually produce and maintain the different permutations of function signatures? I'm hoping that isn't the best answer, or that a tool exists to automate this dreary and error prone task.

Apologies for vagueness, let me give a toy example. Suppose I have need to add two numbers. The function might look something like this:

float add(float x,float y){
   return x+y;
}

Ok that's great for floats, but what if I need it for a wide range of types on which arithmetic is available. Ok I can do this

float add_f(float x,float y){...}
double add_lf(double x,double y){...}
unsigned int add_ui(unsigned int x, unsigned int y){...}

and so forth. If for some (probably stupid) reason I decide I need to also write the contents of the arguments to a binary file, I now have to add in the requisite file I/O code in every single function. Is there a simple way/tool to take an add function and spit out different ones with name mangling to avoid this annoying situation?

Basically in my m4 cases I would just find/replace a macro TYPE with the requisite type, and have a macro MANGLE() which mangles the functions, then I point the output to an alternate .c file. My m4 skills are lacking though.

Function pointers can help with the ultimate interface of my code, but eventually those pointers have to point to something, and then we're just enumerating all the possibilities again. I'm also unclear on how this might affect potential inlining of short functions.

Upvotes: 3

Views: 634

Answers (4)

Andreas Grapentin
Andreas Grapentin

Reputation: 5796

I know that most C developers are afraid of it, but have you thought about using macros?

specific to your example:

// floatstuff.h
float add_f(float x,float y);
double add_lf(double x,double y);
unsigned int add_ui(unsigned int x, unsigned int y);

combined with:

// floatstuff.c
#define MY_CODE \
  return x + y

float
add (float x, float y)
{
  MY_CODE;
}

double
add_lf (double x, double y)
{
  MY_CODE;
}

unsigned int
add_ui (unsigned int x, unsigned int y)
{
  MY_CODE;
}

If the code you are using per function is truly identical, then this might be the solution you are looking for. It avoids most of the code duplication, maintains some degree of readability and has no impact on your runtime. Also, if you keep the macro local to your .c file, you are unlikely to break anything, so no worries there either.

Also, you can do even more weird stuff using parameterized macros, which can give you even more reduced code duplication.

Upvotes: 1

ldav1s
ldav1s

Reputation: 16315

I use GNU autogen for code generation tasks, which sounds somewhat like your current m4 solution, but might be better organized. For example:

type.def

autogen definitions type;

type = { name="int"; mangle="i"; };
type = { name="double"; mangle="lf"; };
type = { name="float"; mangle="f"; };
type = { name="unsigned int"; mangle="ui"; };

type.tpl

[+ autogen5 template 
c=%s.c
(setenv "SHELL" "/bin/sh") +]/*
[+ (dne "* " "* ") +]
*/
[+
 FOR type "\n" +][+name+] add_[+mangle+]([+name+] x, [+name+] y) { ... }[+ENDFOR+]

or something like that. This should spit out a function for each of the types in type.def looking something like:

unsigned int add_ui(unsigned int x, unsigned int y) { ... }

You can also have it insert type-specific code in certain places if needed, etc. You could have it output the add functions described above as well as the I/O versions. You'd have to compute the text for mangle instead of what I've got, but that's not a problem. You'd also have some conditional code for the I/O and a way to toggle the condition on and off (again, not a problem).

I'd definitely try and see if there was some way to generalize the algorithm, but this approach might have drawbacks (e.g. performance issues from not having the real underlying type) as well. But it sounds from the comments that this approach might not work for you.

Upvotes: 1

Pekka
Pekka

Reputation: 3654

You appear to be asking for generic type support. While the macro processing can work in restricted domains, what you are doing is complex.

If the variants are so similar that simply type and name mangling is enough, then could you not use regular C #defines before each of multiple inclusions of the same source fragment to allow the preprocessor perform the substitution? This way, at least there is only a single environment to manage.

Alternately, if the performance hit is not substantial, could you prepare multiple stub functions for each specialisation and map these to a generic version that can be called from the stubs?

Upvotes: 2

Guntram Blohm
Guntram Blohm

Reputation: 9819

The only thing i can think of is: make the algorithm itself independent of the type, have the user of your function create his own function to handle the type-specific parts, and make one of the parameters to your function a pointer to the "handler function".

See the definition/implementation of the qsort routine for what i mean. Qsort works for all kinds of data, but handles the data itself transparently - the only things you pass to qsort is the size of each entry, and a function pointer to a function that does the real comparison.

Upvotes: 3

Related Questions