Since C23, you can do something with the new __VA_OPT__, if you are willing to allow a macro to represent your function call. Combined with _Generic, you can use the macro foo to call one of the other of your foo variants.
CAVEAT EMPTOR / NOTE BENE : Most of this code is tested, but only lightly.
Your question states "zero" arguments, but neither foo_1 or foo_2 was defined to accept no arguments. In the implementation below, if the foo macro detects 0 arguments, it will pass NULL to foo_1. Since foo_1 does not provide a parameter to indicate the length of the array, you will need to define a convention for doing so (as indicated in another answer, using a NULL pointer array element is a common idiom, but there are other ways).
#define join__(A, B) A ## B ## __
#define foo(...) \
join__(foo_help1, __VA_OPT__(1))(__VA_ARGS__)
#define foo_help1__() foo_1(0)
#define foo_help11__(FIRST, ...) \
join__(foo_help2, __VA_OPT__(1))(FIRST __VA_OPT__(,) __VA_ARGS__)
#define foo_help2__(FIRST) \
_Generic((FIRST), \
const char **: foo_1, \
default: foo_3)(FIRST)
#define foo_help21__(...) foo_2(__VA_ARGS__, (const char *)0)
static inline void foo_3 (const char *arg) {
foo_help21__(arg);
}
Try it online!
A NULL pointer value is preemptively added if foo_2 is called. The helper function foo_3 is created for the case of a single string argument, which delegates to foo_2 with the added NULL. Using a distinguishing sentinel value is also an idiomatic technique to indicate the end of the variadic arguments.
For bar, there is no syntax to indicate optional arguments before a required argument. However, since all arguments are the same type, you can just define bar as a regular variable argument function, and your code only needs to be able to find that argument. You can simply scan the arguments until you see the last one (the code below illustrates with a NULL pointer value to indicate the end of the variadic arguments).
#define bar(...) bar_3(__VA_ARGS__ __VA_OPT__(,) (const char *)0)
void bar_3(const char *first, ...) {
const char **argv;
int required = 0, count = 0;
va_list args;
const char *x;
assert(first != 0);
++count;
va_start(args, first);
for (;;) {
x = va_arg(args, const char *);
if (x == 0) break;
++count;
}
va_end(args);
fprintf(stderr, "count: %d\n", count);
argv = calloc(count, sizeof(const char *));
assert(argv != 0);
count = 0;
argv[count++] = first;
va_start(args, first);
for (;;) {
x = va_arg(args, const char *);
if (x == 0) break;
argv[count++] = x;
}
va_end(args);
required = count - 1;
Do_Something(argv, required);
free(argv);
}
Try it online!
The function above stashed the arguments into an allocated array after first counting the number of passed in arguments. It is quite a bit of code, and it iterates over the arguments twice.
A simpler approach may be just to pass in an array of arguments. You can use a macro to help do that if you really want the appearance of passing in the arguments as function parameters.
#define bar(...) bar_4((const char *[]){__VA_ARGS__ __VA_OPT__(,) NULL})
void bar_4(const char **argv);
This same technique might be used for your foo implementation as well, so that all invocations end at foo_1.
#define join__(A, B) A ## B ## __
#define foo(...) \
join__(foo_help1, __VA_OPT__(1))(__VA_ARGS__)
#define foo_help1__() foo_1(0)
#define foo_help11__(FIRST, ...) \
join__(foo_help2, __VA_OPT__(1))(FIRST __VA_OPT__(,) __VA_ARGS__)
#define foo_help2__(FIRST) \
_Generic((FIRST), \
const char **: foo_1, \
default: foo_3)(FIRST)
#define foo_help21__(...) foo_1((const char *[]){__VA_ARGS__, 0})
static inline void foo_3 (const char *arg) {
foo_help21__(arg);
}
Try it online!
The only change from the original solution above is that the helper macro foo_help21__ was rewritten to invoke foo_1.
bar_3is impossible. Honestly, all three are impossible unless the last argument is mandated to be NULL.argcandargvto the subfunction and parse them there usinggetoptorgetopt_long.