Search code examples
cvariablesmacrosc-preprocessorternary

macro with ternary operator - can I funnel through variable at runtime?


I was wondering if I can funnel a non-static variable through a preprocessor macro. Imagine I have to call a function that takes 4 arguments like foo(A,B,C,D); and my input is bar which is an integer value between 1 and 4 and baz which could by any integer value at runtime which goes into A if 'bar' == 1, B if it' == 2 ... D if it's == 4. I came up with a macro that looks something like:

#define setACports(p,v) ((p) == 4 ? ", , ,(v),"  : \
                        ((p) == 3 ? ", ,(v), ,"  : \
                        ((p) == 2 ? ",(v), , ,"  : \
                       (((p) == 1 ? "(v), , , ," : "invalid")))))

/*example application */
void foo(int var1,int var2,int var3, int var4);                           
int main (void) {
    int i = 0;
    for (i=0;i<5;i++) 
        foo(setACports(i,i));
}

Now, while the argument "positioning" work fine, the value obviously does not work correctly because (v) is not available at pre-processor time. How would I implement something like this best? It's a puzzle to me what might be the best solution, any suggestions or hints would be great!


Solution

  • You could do:

    #include <stdio.h>
    
    #define setACports(p, v) \
      (p) == 1 ?(v) :0, \
      (p) == 2 ?(v) :0, \
      (p) == 3 ?(v) :0, \
      (p) == 4 ?(v) :0
    
    void foo(int var1, int var2, int var3, int var4)
    {
      printf("var1 = %d, var2 = %d, var3 = %d, var4 = %d\n", 
        var1, var2, var3, var4);
    }
    
    int main(void) 
    {
      for (int i = 0; i < 5; ++i) 
      {
        printf("i = %d: ", i);
        foo(setACports(i, i));
      }
    }
    

    This outputs:

    i = 0: var1 = 0, var2 = 0, var3 = 0, var4 = 0
    i = 1: var1 = 1, var2 = 0, var3 = 0, var4 = 0
    i = 2: var1 = 0, var2 = 2, var3 = 0, var4 = 0
    i = 3: var1 = 0, var2 = 0, var3 = 3, var4 = 0
    i = 4: var1 = 0, var2 = 0, var3 = 0, var4 = 4
    

    To detect the case of p<1 || p>4 inside the macro you could modify one of the ternary-operations to look like this (the 1st one in this case):

      (p) == 1 \
        ?(v) \
        :((p) < 1 || (p) > 4 \
          ?fflush(stdout), fprintf(stderr, "Invalid: %d", v), exit(EXIT_FAILURE), -1 \
          :0), \
    

    The output then looked like this:

    i = 0: Invalid: 0
    

    Please note that the macro cannot skip the function call, but only either call the function or end the program, as the example above does.

    To be able to continue running one could modify the macro to actually do the function call:

    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    
    #define setACports2(f, p, v) ( \
      ((p) < 1 || (p) > 4) \
        ? fflush(stdout), errno = ERANGE, perror(#f "()"), -1 \
        : ( \
          f( \
            (p) == 1 ?(v) :0, \
            (p) == 2 ?(v) :0, \
            (p) == 3 ?(v) :0, \
            (p) == 4 ?(v) :0 \
          ), 0 \
        ) \
      )
    
    void foo(int var1, int var2, int var3, int var4)
    {
      printf("var1 = %d, var2 = %d, var3 = %d, var4 = %d\n",
        var1, var2, var3, var4);
    }
    
    int main(void)
    {
      for (int i = 0; i < 5; ++i)
      {
        int vi = i;
        printf("i = %d: ", i);
        if (-1 == setACports2(foo, i, vi))
        {
          fprintf(stderr, "setACports() failed\n");
        }
      }
    }
    

    This would output:

    i = 0: foo(): Numerical result out of range
    setACports() failed
    i = 1: var1 = 1, var2 = 0, var3 = 0, var4 = 0
    i = 2: var1 = 0, var2 = 2, var3 = 0, var4 = 0
    i = 3: var1 = 0, var2 = 0, var3 = 3, var4 = 0
    i = 4: var1 = 0, var2 = 0, var3 = 0, var4 = 4
    

    This line

        if (-1 == setACports2(foo, i, vi))
    

    would expand to:

        if (-1 == ( ((i) < 1 || (i) > 4) ? fflush(stdout), errno = ERANGE, perror("foo" "()"), -1 : ( foo((i) == 1 ?(vi) :0, (i) == 2 ?(vi) :0, (i) == 3 ?(vi) :0, (i) == 4 ?(vi) :0), 0 ) ))
    

    As setACports2 duplicates setACports it could also look like this:

    #define setACports2(f, p, v) ( \
      ((p) < 1 || (p) > 4) \
        ? fflush(stdout), errno = ERANGE, perror(#f "()"), -1 \
        : ( \
          f(setACports(p, v)), 0 \
        ) \
      )
    

    Further readings related to this answer: