Search code examples
cfunction-pointersfunction-prototypes

Function lookup table with different function prototypes


What is the best way to call a specified function based on user input except from series of if and strcmp?

For example:

p 2 2 -> call func_p(2, 2)
a 8   -> call func_a(7)
m     -> call func_m(void)

I know it's easy and elegant to make a lookup table consisting of function pointers with the same prototype but how about different prototypes? I thought about using ... in a prototype but I don't know if it's a good solution.


Solution

  • Define all the functions so they take a single array argument.

    Comment from Barmar

    Unifying all functions to the same prototype is exactly what one normally does in this case, though I'd go with a prototype with two parameters: A pointer to an array with the real parameters as well as it's size. That way not every function has to split/parse its arguments on its own.

    I really like stuff like this, so I made a short demo. I made this on my mobile, so it's a bit rough and would need some improvements if used in the wild (memory management and error detection for example). Here it is:

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>
    #include <string.h>
    
    // a node in the abstract syntax tree. Either a
    // value or a call
    struct Ast {
      bool isCall;
      union {
        int value;
        struct {
          char const * operator;
          size_t countOperands;
          struct Ast * operands;
        } call;
      };
    };
    
    // unified function type. Could've also passed an
    // int array, but then evaluate would've needed
    // a memory allocation, so ...
    typedef int (*Function)(struct Ast *, size_t);
    
    
    // implementation of + function. Sums the values of
    // parameters. (which are hopefully evaluated)
    int sum(struct Ast * parameters, size_t num) {
      int result = 0;
      while (num > 0) {
        --num;
        result += parameters [num]. value;
      }
      return result;
    }
    
    // implementation of ? function, ignores any
    // parameters and just asks for an integer.
    int ask (struct Ast * parameters, size_t num) {
      int value;
      scanf("%d", & value);
      return value;
    }
    
    // poor man's lookup table
    static Function const functions [] = {sum, ask};
    static char const * const function_names [] = {"+", "?"};
    
    // poor man's lookup from above static arrays
    Function lookup (char const * name) {
      size_t it = sizeof (functions) / sizeof (functions [0]);
      while (it > 0) {
        --it;
        if (strcmp(name, function_names [it]) == 0) {
          return functions [it];
        }
      }
      exit(1);
    }
    
    // evaluate an Ast. Normally one wouldn't return
    // an Ast node but rather some value_t (assuming
    // dynamic typing)
    // this function is also destructive on call Ast nodes,
    // in order to get around any memory management.
    // so be careful!
    struct Ast * evaluate (struct Ast * node) {
      if (! node->isCall) {
        // anything that's not a call is a value, thus
        // self evaluating, return it unchanged!
        return node;
      }
      // so it's a call. Get the associated function from
      // the lookup table!
      Function f = lookup(node->call.operator);
      // unconditionally evaluate all operands of the call.
      // thus no macros or conditionals, sorry!
      size_t o;
      for (o = 0; o < node->call.countOperands; ++o) {
        // destructive!
        node->call.operands[o] = *evaluate(&(node->call.operands[o]));
      }
      // use the call node to store the result value.
      // this will blow up if any call node uses any
      // allocated memory!
      node->isCall = false;
      // call the function with the evaluated operands and
      // store the result
      node->value = f(node->call.operands, node->call.countOperands);
      return node;
    }
    
    int main () {
      // I didn't want to write a parser, so here's a
      // static Ast of (+ 21 10 (?))
      struct Ast nodes [] = {
        {.isCall=false, .value=21},
        {.isCall=false, .value=10},
        {.isCall=true, .call = {
            .operator="?", .countOperands=0}},
        {.isCall=true, .call = {
            .operator="+", .countOperands=3,
            .operands=nodes}}};
      struct Ast * result = evaluate(&(nodes [3]));
      printf("(+ 21 10 (?)) => %d\n", result->value);
      return 0;
    }
    

    Written and "tested" on ideone.

    A different approach would be to use a void * tagged with some function type information. But it's rather difficult to pass the actual parameters to functions encoded like that, and it also doesn't scale well.