1

Say I have a function like this defined in a C++ header:

namespace foo {
    void bar(int a, int b = 1);
}

and I would like to use this function in C code. One obvious solution would be to define two functions like this:

void foo_bar_1(int a)
{ foo::bar(a, 1); }

void foo_bar_2(int a, int b)
{ foo::bar(a, b); }

These can then easily be included in C code. However, this gets ugly for multiple default parameters, it would be nicer to have a single wrapper function. I thought about doing something like this:

#define _test_foo_numargs(...) (sizeof((int[]){__VA_ARGS__})/sizeof(int))

#define test_foo(...) do { \
  if (_test_foo_numargs(__VA_ARGS__) == 1) \
    test_foo_1(__VA_ARGS__); \
  else if (_test_foo_numargs(__VA_ARGS__) == 2) \
    test_foo_2(__VA_ARGS__); \
} while (0)

But that doesn't work because both of the calls to test_foo_1 and test_foo_2 have to be valid in order for this to compile.

Is there a better way to do this?

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
Peter
  • 2,919
  • 1
  • 16
  • 35
  • 1
    You could name foo_bar_1and 2 to just foo_bar if that works for you. Because then the compiler disieds wich function is called up on the number of arguments. – Gian Laager Dec 27 '20 at 09:12
  • 2
    @GianLaager: But C doesn't support overloading, that's the crux here. – Peter Dec 27 '20 at 09:14
  • Clearly a better way is simply not to try to emulate default arguments in c. – vmt Dec 27 '20 at 09:26
  • @vmt: I'm not trying to emulate anything, I'm wrapping C++ code written by someone else who was seemingly very fond of default parameters and want to avoid code duplication. – Peter Dec 27 '20 at 09:27
  • Well, in effect you are trying to emulate the behavior. What I mean is, the cleanest solution is just to have your wrapper take both args and pass them. – vmt Dec 27 '20 at 09:31
  • @vmt: I guess that makes sense although I did sort of find a solution now. – Peter Dec 27 '20 at 09:33
  • Did you consider *generating* some glue code, in the spirit of [SWIG](https://swig.org/) ? – Basile Starynkevitch Dec 27 '20 at 10:04
  • Your way to count argument only works if all arguments are of the same type... There exists ways to count argument of variadic macro (up to an hard-coded limit). – Jarod42 Dec 27 '20 at 11:07

2 Answers2

1

I will provide my own solution here in case nobody has a better one and someone has the same problem in the future:

#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>

int _test_foo_1(int a)
{ return a; }

int _test_foo_2(int a, int b)
{ return a + b; }

int _test_foo_va(size_t num_args, ...)
{
  va_list args;
  va_start(args, num_args);

  switch (num_args) {
  case 1:
    return _test_foo_1(va_arg(args, int));
  case 2:
    return _test_foo_2(va_arg(args, int), va_arg(args, int));
  }

  va_end(args);
}

#define _test_foo_numargs(...) (sizeof((int[]){__VA_ARGS__})/sizeof(int))

#define test_foo(...) _test_foo_va(_test_foo_numargs(__VA_ARGS__), __VA_ARGS__)

int main()
{
  printf("%d\n", test_foo(1));
  printf("%d\n", test_foo(1, 2));
}

This is of course pretty unsafe because it will compile if too little or too many arguments are passed.

Peter
  • 2,919
  • 1
  • 16
  • 35
  • With `_test_foo_2(va_arg(args, int), va_arg(args, int));`, you might have argument in reverse order, as order of evaluation of parameter is indertermined. You have to use intermediate variable: `auto arg1 = va_arg(args, int); auto arg2 = va_arg(args, int); _test_foo_2(arg1, arg2);`. – Jarod42 Dec 27 '20 at 11:05
0

You might do the following:

#define TAKE_9(_1, _2, _3, _4, _5, _6, _7, _8, _9, ...) _9
#define COUNT(...) TAKE_9(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1)

#define CONCAT_(A, B) A ## B
#define CONCAT(A, B) CONCAT_(A, B)

#define SELECT(NAME, ...) CONCAT(NAME, COUNT(__VA_ARGS__))(__VA_ARGS__)

#define foo_bar(...) SELECT(foo_bar_, __VA_ARGS__)

Demo

COUNT can be upgraded to handle 0 arguments, if you compiler support it

#define COUNT(...) TAKE_9(__VA_ARGS__ __VA_OPT__(,) 8, 7, 6, 5, 4, 3, 2, 1, 0)

Demo

To avoid to have to write foo_bar_N, you might do:

// ...
#define FUNC_WITH_DEFAULT_ARG_2(func, DEFAULT, ...) TAKE_3(__VA_ARGS__ __VA_OPT__(,) \
                                                 func(__VA_ARGS__), \
                                                 func(__VA_ARGS__, TAKE_2 DEFAULT), \
                                                 func(TAKE_1 DEFAULT, TAKE_2 DEFAULT) )

#define FUNC_WITH_DEFAULT_ARG_3(func, DEFAULT, ...) TAKE_4(__VA_ARGS__ __VA_OPT__(,) \
                                                 func(__VA_ARGS__), \
                                                 func(__VA_ARGS__, TAKE_2 DEFAULT), \
                                                 func(__VA_ARGS__, TAKE_2 DEFAULT, TAKE_3 DEFAULT), \
                                                 func(TAKE_1 DEFAULT, TAKE_2 DEFAULT, TAKE_3 DEFAULT) )

// Choose on or other, error message for misuse might differ
// RequiredParameter is just a name for "better" error message when not enough parameter are given
#define foo_bar(...) FUNC_WITH_DEFAULT_ARG_2(foo_bar_impl, (RequiredParameter, 1), __VA_ARGS__)
#define foo_bar2(_1, ...) FUNC_WITH_DEFAULT_ARG_3(foo_bar_impl, (_1, 0), __VA_ARGS__)

void foo_bar_impl(int a, int b)
{ foo::bar(a, b); }

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302