5

I want to make an function which will take variable number of arguments of different types. My problem is how can i parse the argument types. Like caller can pass arguments of any type in any order (without specifying format specifier). So at the calling function how can i differentiate between types.

How to print all values passed by the caller in the function var_argument ?

Let say I want to do sprintf inside var_argument function of the parameters passed by the caller

Here is my C code

#include <stdio.h>
#include <stdarg.h>
void var_argument(int num,...);

int main()
{
 var_argument(3,"Hello",7.87f,6);
 var_argument(3,7.87f,"Hello",6);
 return 0;
}

void var_argument(int num,...)
{
 char str[80];
/* What code should I write to differentiate between types and to print all the values passed by the caller */     

 va_list list;
 va_start(list,num);

 /* How to do sprintf of the parameter passed to this function and store 
 the formatted output in the array str  */
}
vivek
  • 467
  • 2
  • 6
  • 18
  • Do you know about `printf()`? – Iharob Al Asimi Oct 03 '17 at 14:44
  • You need another parameter to give you the types of the arguments, that's the only (portable) way, e.g. like `printf` which deduces the type from the format specific (`%d`, `%f`, ...). – Holt Oct 03 '17 at 14:45
  • the argument before the `...` is traditionally meant to be something that indicates the types and the size is inferred from that – Chris Turner Oct 03 '17 at 14:45
  • 2
    "I want to make an function which will take variable number of arguments of different types" Why? The need for a variadic function is almost always caused by muddy design - this is a [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Lundin Oct 03 '17 at 14:45
  • @Lundin I would say, "*often*" instead of "*almost always*", because there are cases where it's actually a very elegant solution. For example, executing SQL prepared statements for *mysql*'s c library is a pain in the a**, but using a variadic function it can become a very simple thing to do. – Iharob Al Asimi Oct 03 '17 at 14:53

1 Answers1

7

This is somehow already done, for example printf() has such mechanism. For variable arguments functions, you need a first parameter that is not part of the variable parameters, use it as a format string to determine the type of parameter x at position n.

Because the first parameter is mandatory, so you MUST have such a parameter and it seems natural to use it as an indicator of what to expect in the variable arguments.

Take this as an example,

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

int
variable(const char *format, ...)
{
    va_list args;
    int count;

    va_start(args, format);

    count = 0;
    while (*format != '\0') {
        switch (*format++) {
            case 's':
                fprintf(stdout, "arg[%d]: %s\n", count, va_arg(args, const char *));
                break;
            case 'd':
                fprintf(stdout, "arg[%d]: %d\n", count, va_arg(args, int));
                break;
            case 'f':
                fprintf(stdout, "arg[%d]: %f\n", count, va_arg(args, double));
                break;
        }
        count += 1;
    }
    va_end(args);
    return 0;
}

int
main(void)
{
    variable("sdf", "Example", 3, 3.1416);
}

Of course, any mismatch between the specifiers and the actual types of the arguments will lead to undefined behavior and as a logical consequence to undesired behavior. So you must be very careful or, use printf() style specifiers and tell the compiler to warn if such mismatch occurs.

Another solution, is to do it like some of glib's functions, pass a type specifier and immediately after the parameter, with a last value that would indicate the end of the parameters. Parameter "specifiers" could be an enum enumerating all supported types, like this

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

enum types {
    String, Integer, Double, Float = Double, /* unless you want to store the value in a poitner
                                              * there is normally no difference between these
                                              */
    End
};

int
variable(enum types type, ...)
{
    va_list args;
    int count;

    va_start(args, type);

    count = 0;
    while (type != End) {
        switch (type) {
            case String:
                fprintf(stdout, "arg[%d]: %s\n", count, va_arg(args, const char *));
                break;
            case Integer:
                fprintf(stdout, "arg[%d]: %d\n", count, va_arg(args, int));
                break;
            case Double:
                fprintf(stdout, "arg[%d]: %f\n", count, va_arg(args, double));
                break;
            default:
                fprintf(stderr, "unknown type specifier\n");
                break;
        }
        type = va_arg(args, enum types);
        count += 1;
    }

    va_end(args);
    return 0;
}

int
main(void)
{
    variable(String, "Example", Integer, 3, Double, 3.1416, End);
}
Iharob Al Asimi
  • 52,653
  • 6
  • 59
  • 97
  • You can also use [_Generic](https://stackoverflow.com/a/28897994/975097) to get the variable's type, instead of passing the type as a parameter. – Anderson Green Jul 15 '21 at 13:12