3

I am trying to make a function with a variable number of arguments but only want two possible number of arguments for the function, either 7 or 11. I want to trigger a preprocessor error and quit compiling if there are not 7 or 11 arguments. I know it may seem silly to not just throw a printf with a warning during the program execution but this code is being written for a microcontoller and will not be running on a computer where it can give such a warning so I would like the compiler to catch this and warn me if possible.

void LCD_Attach(int num,...){
va_list valist;
va_start(valist, num);
if (num = 7)
{
    //do something
}
else if (num = 11)
{
    //do something else
} 
else
{
    #error LCD_Attach must have 7 or 11 arguments
}
va_end(valist); 

}

I am aware this code will not work as written because when compiling the compiler always catches the #error and throws the error and the if else statement only works after compilation during execution. I cant use the preprocessor #if either because it cant read the variable num. There seems to be a clear divider between the preprocessor statements and the code that runs during execution. Am I approaching this problem completely wrong?

  • `va_list` and friends are looked at after all preprocessor directives, so you can't do that. – François Andrieux Jan 08 '18 at 21:02
  • 13
    *Am I approaching this problem completely wrong* - yes, you are. If you want a function with 7 or 11 parameters, make two functions with 7 and 11 parameters. Or a function accepting `union` with the possible combinations. – Eugene Sh. Jan 08 '18 at 21:02
  • 1
    The question states c, but you tagged c++ and c. If you are in fact using c++ you may want to consider parameter packs, specialization and `static_assert` instead. If not, please fix your tags. – François Andrieux Jan 08 '18 at 21:02
  • be sure your environment supports 11 function arguments... if not you'll need to put everything in a `struct` and pass that instead. Perhaps you should do that anyway in the interest of code readability, particularly if you're making a lot of these function calls. – yano Jan 08 '18 at 21:20
  • "I want to trigger a preprocessor error and quit compiling if there are not 7 or 11 arguments." Yet the question does not post sample usages of 7 or 11 arguments. That is where the _preprocessor error_ needs to occur, not in the definition of `LCD_Attach()`. Post sample usages of _calling_ `LCD_Attach()`. It does not make sense to seek a _preprocessor error_ during run-time of `LCD_Attach()`. – chux - Reinstate Monica Jan 08 '18 at 21:34

1 Answers1

3

You should consider the possibility of giving the 7-argument and 11-argument forms of LCD_Attach different names; then you can use normal argument lists for both. That will be more maintainable and will catch more problems at compile time.

However, what you asked for can be done. If all of the variadic arguments to LCD_Attach have the same type, you can use the argument-counting technique from this old answer plus C11's static_assert:

#include <assert.h>
#include <stdarg.h>

// Replace `int` in this macro with whatever the concrete type of all
// the arguments to `LCD_Attach` actually is.
#define NUMARGS(...)  (sizeof((int[]){__VA_ARGS__})/sizeof(int))

#define LCD_Attach(...) do { \
  static_assert(NUMARGS(__VA_ARGS__) == 7 || NUMARGS(__VA_ARGS__) == 11, \
                "wrong number of arguments to LCD_Attach"); \
  LCD_Attach(NUMARGS(__VA_ARGS__), __VA_ARGS__); \
} while (0)

void (LCD_Attach)(int num, ...)
{
   assert(num == 7 || num == 11); // runtime backstop, should never fire
   va_list ap;
   va_start(ap, num);
   if (num == 7) {
     // do something
   } else {
     // do something else
   }
   va_end(ap);
}

void test_7(void) { LCD_Attach(1, 2, 3, 4, 5, 6, 7); }
void test_11(void) { LCD_Attach(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); }
void test_wrong(void) { LCD_Attach(1, 2, 3, 4, 5, 6); }

=>

$ gcc -fsyntax-only -std=c11 -pedantic -Wall test.c
In file included from test.c:1:0:
test.c: In function ‘test_wrong’:
test.c:10:3: error: static assertion failed:
  "wrong number of arguments to LCD_Attach"
   static_assert(NUMARGS(__VA_ARGS__) == 7 || NUMARGS(__VA_ARGS__) == 11, \
   ^
test.c:31:25: note: in expansion of macro ‘LCD_Attach’
 void test_wrong(void) { LCD_Attach(1, 2, 3, 4, 5, 6); }
                         ^~~~~~~~~~

If they don't all have the same type, you have to use the more complicated argument-counting technique from this other answer to the same question, but the principle is the same.

zwol
  • 135,547
  • 38
  • 252
  • 361