3

Here is the variadic example:

https://en.cppreference.com/w/c/variadic.html

I want to define two functions but I don't know how to do it correctly.

The "foo" function should accept "zero" or "multiple" string arguments.

void foo_1(const char **argv);

void foo_2(const char* argv, ...);

The "bar" function should accept "zero" or "multiple" optional string arguments at first, and then accept "one mandatory" string argument at last:

void bar_3(...optional_strings_at_first..., const char* mandatory_string_at_last);

What's the difference between "foo_1" and "foo_2"? And how to implement the "bar_3"? Is the "bar_3" possible or not?

15
  • 3
    bar_3 is impossible. Honestly, all three are impossible unless the last argument is mandated to be NULL. Commented Sep 16 at 3:09
  • 2
    Then pass argc and argv to the subfunction and parse them there using getopt or getopt_long. Commented Sep 16 at 3:32
  • 1
    @3CEZVQ "in the C programming language, a function cannot be declared with only an ellipsis (...) as its parameter list." is outdated. C23 allows it. Commented Sep 16 at 3:47
  • 1
    @chux Good to know about it. Commented Sep 16 at 3:48
  • 3
    The need for variadic functions in the first place always comes from bad program design. The correct answer is always: fix your program design instead. Commented Sep 16 at 6:42

3 Answers 3

5

The "foo" function should accept "zero" or "multiple" string arguments.

C23 allows void foo(...);


The "bar" function should accept "zero" or "multiple" optional string arguments at first, and then accept "one mandatory" string argument at last:

Perhaps a variation on void foo(...);? Else I see no way.

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

6 Comments

But void foo(...){ … } doesn't provide a way to know how many arguments were provided. You have to know, somehow.
Sure, this is part of the function's contract.
Update details. FWIW, I recall using foo(...) with pre C89 compilers (the wild west days) and one case of using a global to control arg count.
Curious — I thought ... was a new feature of C89, along with the <stdarg.h>. Prior to that, you didn't have function prototypes, and you used the <varargs.h> header. POSIX 1997 <varargs.h> documents it as legacy, but shows an example of how it might be used. I assume that there were some compilers that implemented what became the standard — on the whole, the C89 committee were careful not to invent things. So, you may have used a compiler with … well, is it an extension when there isn't yet a standard to extend?
Brian W Kernighan and Dennis M Ritchie The C Programming Language, 1st Edn (1978) mentions variable-length argument lists on p71 (and they're mentioned in conjunction with discussions of printf() and scanf()), but it does not index <varargs.h> or show any example of how to use it. And <stdarg.h> was about 10 years in the future when that was written.
FYI: my earliest use of C and f(...) was mid 1980s. "Prior to that, you didn't have function prototypes" --> I recall trying to make code portable among DOS, windows, Unix (BSD and others), Aegis, HP-UX, AIX and so coding all functions with 2-3 declaration/definition styles (via #if). For fun I recall using a CPP pre-processor to convert C++ to C and then compiling.
4

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

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

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.

This same technique might be used for your foo implementation as well, so that all invocations end at foo_1.

Try it online!

The only change from the original solution above is that the helper macro foo_help21__ was rewritten to invoke foo_1.

Comments

3
void foo_1(const char **argv);

It's impossible to know the array size. So, it must be required that the last array element is the NULL pointer.


void foo_2(const char* argv, ...);

The similarity is here; it's impossible to know the number of arguments. So, it must be required that the last argument is the NULL pointer. It can be simplified a bit:

void foo_2_(const char* argv, ...);
#define foo_2(...) foo_2_(__VA_ARGS__, NULL)

The possible implementation:

void foo_2_(const char* argv, ...) {
  if (!argv)
    return;
  va_list args;
  va_start(args, argv);

  const char* arg;
  while ((arg = va_arg(args, const char*)) != NULL)
    // do smth
  }
  va_end(args);
}

void bar_3(...optional_strings_at_first..., const char* mandatory_string_at_last);

This is simply not allowed, the ellipsis must be the last parameter.

5 Comments

Concerning foo_1(), "So, it must be required that the last array element is the NULL pointer." is not true. I can have any "contract" for this function, including for example that the first pointer specifies the number of additional pointers. Or any other method.
You are welcome to post an own answer with argv[0] = (const char *)N and we will see review feedbacks.
I did not say that the number should be cast to a pointer. It could as well be a format string like for printf(). There are uncountable possibilities.
That is not a rebuttal, and votes do not determine correctness. As a matter of fact, it is false that the only way to determine the number of arguments is by making the last argument a null pointer. (Further, NULL is a different thing from a null pointer.) Correctness should be a primary criterion for quality answers.
A possible approach for bar_3 is to make it just like the macro approach to foo_2, but the while loop hunts down the required last string.

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.