1

I am trying to build a parser to a given input, there are 8 possible commands. So I figured that instead of using the ugly technique of a case switch block like that:

switch(command)
case cmd1:
.... /*call a function that do cmd1*/
case cmd2
..../*call a function that do cmd2*/

I will define in a header an array of structs, each one contains the name of the function, and a pointer to a function:

typedef struct command_info
{
    char *name;
    void (*func)(int)
};
command_info command_table[] = {{"func1", &func1}, {"func2", &func2} }

So that I can switch to the more elegant:

int i;
for(i = 0; i < COMMAND_TABLE_LENGTH; i++)
  if(!strcmp(command_table[i].name, command))
     command_table[i].func(2);

My only problem is, that the functions have different parameters (all return void). This is not a problem for me since I can check if the function is func1 or func2 search for one int argument for example, and if it is func3 or func4 search for two (still more compact than case switch). But the function pointer only points to a function with a certain type and amount of arguments. How can I make a universal pointer that can point to any function?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Aviv Aviv
  • 129
  • 1
  • 10
  • 1
    There are ways to build a common interface in C, but they often involve `(void *)` pointers and are usually even uglier than `switch`. Do the sensible thing and call individual functions for each command with type-safe arguments. You probably need to parse the argumkents to these commands differently anyway. – M Oehm Mar 23 '20 at 06:14
  • You can't, really. Sure you could possibly solve it through generic pointers (i.e. `void *`) and some type-casting, but it's going to be ugly real quick. It could possible be solved with tagged unions (i.e. structures with some kind of "tag" telling the type, and a `union` member for the functions pointers). This too will become rather ugly and after a while, but at least better than the generic pointer and casting scenario. Or, if it is at all possible, rethink your design, so you don't need something like this in the implementation. – Some programmer dude Mar 23 '20 at 06:15
  • 1
    (But if you happen to write a brainf\*ck interpreter, you can pass a pointer to a common state structure to all functions. Or use `void (*)(void)` and operate on global state.) – M Oehm Mar 23 '20 at 06:15
  • @MOehm No this is not for BF, it is for some assigment – Aviv Aviv Mar 23 '20 at 06:17
  • Well, that was just a guess, because BF interpterers are a common programming exercise and because "there are 8 possible commands". `:)` – M Oehm Mar 23 '20 at 06:18
  • @MOehm and Some programmer dude, the thing is that if I use case switch, I will break the DRY rule many times because I will have to right the code that reads the same arguments from the same function types again and again and this is much uglier. so i prefer weak typing. And I can't use function to collect the argument from the string because there are to many different function types – Aviv Aviv Mar 23 '20 at 06:23
  • Hm. What is the function supposed to do? Operate on the input? Parse the input and operate on it? Perhaps you should provide a simplified, but realistic example of what a switch solution to your problem looks like. – M Oehm Mar 23 '20 at 06:26
  • If you use a pointer-to-function type, you must cast the pointer to the correct type before calling the function it points at — otherwise, you get undefined behaviour. That means it may work as you wanted, despite that not being guaranteed, or it may fail horribly. More seriously, if it works on your current system, it may fail horribly in the future. Unless you can force a consistent interface, you _shouldn't_ try using a common function pointer type. – Jonathan Leffler Mar 23 '20 at 06:34
  • @MOehm I deleted the 150 lines code for the case switch statement ): but I can tell you what it supposed to do. You got complex variables named from A - F. and the command do operations on these complex variables. for example, you can type ```read_comp F 2.3 5.7``` and the function will assign F the values 2.3 and 5.7. – Aviv Aviv Mar 23 '20 at 06:34
  • You may just delegate the parsing of arguments to each function ; passing them as arguments what is needed to do that cleanly. – Joël Hecht Mar 23 '20 at 06:34
  • 1
    The relevant portion of the standard is C11 [§6.3.2.3 Pointers ¶8](http://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p8) (part of §6.3 Conversions): _A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined. – Jonathan Leffler Mar 23 '20 at 06:39
  • In that case, why not pass in the rest of the string, so that the function can handle the parsing as well? Then write some general functions that read the data and provide an error message if the data is bad. – M Oehm Mar 23 '20 at 06:39
  • @MOehm But tthen the code won't be modular. – Aviv Aviv Mar 23 '20 at 06:41
  • Why wouldn't it be modular? Each function knows what data to take and cares about it. If you have, for example, an addition function, it takes care of parsing, checking the arguments and carrying out the operation. But perhaps I don't really understand what you want from "modularity". Perhaps your "modularity" is a Holy Grail. If the modules are different in nature, you cannot shoehorn them all into the same mould. (Sorry for the mixed metaphor here.) Again, please provide a realistic example of what you want to achieve. Just two cases are okay if they reflect what you want. – M Oehm Mar 23 '20 at 06:46
  • @MOehm Maybe my understanding of modularity is wrong indeed. if this is modular, then I will adopt this solution. Thank you! – Aviv Aviv Mar 23 '20 at 06:51
  • 1
    Maybe not wrong, but I have the impression that you are trying too hard to make your system modular. Modularity is good and helps you to maintain and extend your program easily, but it only works if there is something common underlying the modules. Perhaps a solution is to have classes of modules: binary operators, unary operators and functions and so on. Or perhaps unary operations are binary operations with a second parameter of null. Printing and setting values are special operations out of the module system. – M Oehm Mar 23 '20 at 07:04
  • `typedef struct command_info { char *name; }` compiles well in C++, not C ("warning: useless storage class specifier in empty declaration"). @Aviv Aviv Are you using a C++ compiler? – chux - Reinstate Monica Mar 23 '20 at 12:08
  • 1
    @Some programmer dude Re: [through generic pointers (i.e. void *)](https://stackoverflow.com/questions/60808663/point-to-functions-with-different-arguments-using-the-same-pointer/60812959#comment107586088_60808663). `void*`, a generic _object_ pointer, may be insufficient to encode a _function_ pointer. No application for `void*` here as the issue is function pointers. – chux - Reinstate Monica Mar 23 '20 at 12:38

1 Answers1

1

But the function pointer only points to a function with a certain type and amount of arguments.
How can I make a universal pointer that can point to any function?

In OP's limited case, use void (*func)().


Any function pointer can be converted with a type cast to another function pointer and retain an equivalent function address. @Jonathan Leffler

int (*foo)(int) = (int (*)(int)) sqrt;
double (*sq)(double) = (double (*)(double)) foo;
printf("%f\n", sq(2));  // prints 1.414214

A function pointer need not provide a function parameter signature.

// No parameter info
//        vv     
int (*foo)() = (int (*)()) sqrt;

OP has "functions have different parameters (all return void)", so in OP's case code could use a limited universal function pointer of void (*func)() and lose parameter checking.

typedef struct {
  char *name;      // suggest const char *name
  void (*func)();  // no parameter info nor checking
} command_info;

char buf[100];
// void setbuf(FILE * restrict stream, char * restrict buf);
command_info fred = { "my_setbuf", setbuf };

// Both compile, 2nd is UB.
fred.func(stdin, buf);  // No parameter checking.
fred.func(0);           // No parameter checking.

Code also incurs a subtle issue when calling .funf(): the parameters ranking lower than int/unsigned are promoted as well as float parameters before passed to the function. Best to make certain the parameters are not char, float, short, _Bool etc. to avoid compatible signature issues.


void * is a universal object pointer. It may be insufficient to encode a function pointer. So it is not a portable candidate. It is not uncommon for the size of a function pointer to be wider than sizeof(void*).

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256