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?

8
  • 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. Commented Dec 27, 2020 at 9:12
  • 2
    @GianLaager: But C doesn't support overloading, that's the crux here. Commented Dec 27, 2020 at 9:14
  • Clearly a better way is simply not to try to emulate default arguments in c. Commented Dec 27, 2020 at 9: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. Commented Dec 27, 2020 at 9: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. Commented Dec 27, 2020 at 9:31

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

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);.
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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.