Home > OS >  using function names as functions in a C macro
using function names as functions in a C macro

Time:07-25

Suppose i have code like this in my program:

            if (!strcmp(current, "sin")) {
            pushFloat(sin(x), &operands);
        } else if (!strcmp(current, "cos")) {
            pushFloat(cos(x), &operands);
        } else if (!strcmp(current, "tan")) {
            pushFloat(tan(x), &operands);
        } else if (!strcmp(current, "ctg")) {
            pushFloat(1. / tan(x), &operands);
        } else if (!strcmp(current, "ln")) {
            pushFloat(log(x), &operands);
        } else if (!strcmp(current, "sqrt")) {
            pushFloat(sqrt(x), &operands);
        }

There are function names such as "sin" or "cos" saved in the current char array

Instead of using this long if block, or replacing it with an even longer switch block, i wanted to write a simple macro like this: #define PUSHFUNC(stack, func, value)(pushFloat(func(value), &stack)) and call it like this PUSHFUNC(operands, current, x)

Doing it this way creates an error "current is not a function or function pointer". I initially thought macros are just text replacement, so if i force a string that is equal to an actual function into a macro, it would expand to the function itself, but looks like i was wrong. Is there a way to achieve what i want using a macro, or should i just write a map block?

CodePudding user response:

I initially thought macros are just text replacement,

That's your problem: macros are just text replacement. So if you have:

#define PUSHFUNC(stack, func, value) (pushFloat(func(value), &stack))

And you write:

PUSHFUNC(operands, current, x)

You get:

(pushFloat(current(value), &operands))

And indeed, you have no function named current. Macros are expanded before your code compiles; the preprocessor has no knowledge of the content of your variables.


If you really want to avoid a long chain of if statements, you could implement some sort of table lookup:

#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <math.h>

typedef double (*floatop)(double x);

typedef struct {
  char *name;
  floatop operation;
} entry;

double ctg(double);

entry opertable[] = {
  {"sin", sin},
  {"cos", cos},
  {"tan", tan},
  {"ctg", ctg},
  {"sqrt", sqrt},
  {NULL, NULL},
};

double ctg(double x) {
    return 1. / tan(x);
}

floatop findop(char *name) {
    int i;

    for (i=0; opertable[i].name; i  ) {
        if (strcmp(opertable[i].name, name) == 0) {
            return opertable[i].operation;
        }
    }
}

int main() {
    float x = 4;

    printf("sin(%f) = %f\n", x, findop("sin")(x));
    printf("sqrt(%f) = %f\n", x, findop("sqrt")(x));
    printf("tan(%f) = %f\n", x, findop("tan")(x));
    printf("ctg(%f) = %f\n", x, findop("ctg")(x));
}

...but this requires that all of your functions take the same arguments, so for things like ctg you would need to add a helper function. You also need to decide if the increased complexity of the table lookup makes sense: it really depends on how many different operation names you expect to implement.

The output of the above code is:

sin(4.000000) = -0.756802
sqrt(4.000000) = 2.000000
tan(4.000000) = 1.157821
ctg(4.000000) = 0.863691

CodePudding user response:

Is there a way to achieve what i want using a macro, or should i just write a map block?

I would recommend using an enum containing symbols for all the functions you might want to call, and using that in a switch-case block, instead of comparing a bunch of strings. Here's a very brief sample that only uses some of the functions you refer to...

enum which_func { SIN, COS, TAN, };

enum which_func which = SIN;

switch (which) {
case SIN:
    pushFloat(sin(x), &operands);
    break;
case COS:
    pushFloat(cos(x), &operands);
    break;
case TAN:
    pushFloat(tan(x), &operands);
    break;
default:
    assert(false); // shouldn't be reachable if enum value is well-defined
}

This version will be easier to maintain in the long run, more efficient to execute and possibly more robust to logic errors (there are some compiler warnings that you can enable which will warn you if you're not handling all enum values, which can help you catch missed cases in your logic).

CodePudding user response:

To add to what other answers said, what you can do is to make a macro that expands to the "basic block" of your if chain, avoiding some repetitions thanks to the stringizing operator:

#define HANDLE_FN_EXPR(fn, expr) \
    else if(!strcmp(current, #fn)) \
        pushFloat((expr), &operands)
#define HANDLE_FN(fn) \
    HANDLE_FN_EXPR(fn, fn(x)) 

Then you can do

if(0);
HANDLE_FN(sin);
HANDLE_FN(cos);
HANDLE_FN(tan);
HANDLE_FN_EXPR(ctg, 1./tan(x));
HANDLE_FN(ln);
HANDLE_FN(sqrt);

CodePudding user response:

Macros do in fact do text replacement. Given your macro definition, this:

PUSHFUNC(operands, current, x)

expands to this:

(pushFloat(current(x), &operands))

So as you can see, the text that is being replaced is the name of the variable, not the text that it contains.

And even if this did work as you expected, it wouldn't be able to properly handle the 1. / tan(x) case.

This means there isn't really a better way to do what you want.

CodePudding user response:

Why not create some objects for each function type? I know, this is C not C , but the idea will still work. First, create the function object type:-

typedef struct _Function
{
  char *name;
  float (*function) (float argument);
} Function;arg

And now create an array of function objects:-

Function functions [] =
{
  { "sin", sin },
  { "cos", cos }
  // and so on
};

where the functions are defined:-

float sin(float x)
{
  return 0; // put correct code here
}
float cos(float x)
{
  return 0; // put correct code here
}

Finally, parse the input:-

for (int i = 0; i < sizeof functions / sizeof functions[0];   i)
{
  if (strcmp(functions[i].name, current) == 0)
  {
    pushFloat(functions[i].function(arg)); // add operands!
    break;
  }
}

I find using enums for stuff like this very hard to maintain! Adding new functions means going through the code to find cases where the enum is used and updating it prone to errors (like missing a place!).

All because it's not C , doesn't mean you can't use objects! It's just there's no language support for it so you have to do a bit more work (and, yeah, there are features missing!)

  • Related