1

I would like to create a common structure that I may use to pass parameters of multiple lengths and types into different functions.

As an example, consider the following structure:

typedef struct _list_t {
    int ID;
    char *fmt;
    int nparams;
} list_t;
list_t infoList[100]; //this will be pre-populated with the operations my app offers 


typedef struct _common {
    int ID;
    char *params;
} common;

A variable size function is used to pass in the parameters given the format is already populated:

int Vfunc(common * c, ...) {
    va_list args;
    va_start(args, c);
    
    //code to search for ID in infoList and fetch its fmt
    char params_buff[100]; //max params is 100
    vsprintf(str_params, fmt, args);

    va_end(args);

    c->params = (char *)malloc(sizeof(char)*(strlen(params_buff)+1));
    strncpy(c->params, params_buff, strlen(params_buff)+1);
}
int execute(common * c) { 
    if (c->ID == 1) { //add 2 numbers
        int x, y; // i expect 2 numbers 
        //code to find ID in infoList and fetch its fmt
        sscanf(c->params, fmt, &x, &y);
    
        return (x + y);
    }
    else if (c->ID == 2) {
    //do another operation, i expect an unsigned char array?
    }
    
}

Main program will look somewhat like this:

int main()
{
    common c;
    c.ID = 1;

    Vfunc(&c, 12, 2);
    
    execute(&c);
    
    return 0;
}

Now I can pass in the structure to any function, which will deal with the parameters appropriately. However I do not see a way to have unsigned char[] as one of the parameters since unsigned char arrays do not have a "format". The format of a char[] would be %s. Basically I want to pass in some raw data through this structure.

Is there a way to do this or a better implementation to fulfill the goal?

EDIT:

It seems that the questions goal is unclear. Say my application can provide arithmetic operations (like a calculator). Say a user of my application wants to add 2 numbers. All I want them to do is fill in this common structure, then pass it into lets say a function to get it executed. All the ID's of the operations will be known from lets say a manual, so the user will know how many parameters they can pass and what ID does what. As the app owner i will be filling infoList with the ID's I offer.

So this is just to give you an idea of what I mean by a "common structure". It can be implemented in other ways too, maybe you have a better way. But my goal is for the implementation to be able to pass in an unsigned char array. Can I do that?

Sarah cartenz
  • 1,313
  • 2
  • 14
  • 37
  • 1
    For the first question you may look up [variable argument list](https://linux.die.net/man/3/stdarg) man page. – kaylum Aug 02 '20 at 06:49
  • Hard to answer without knowing more, but I'd look at an object-oriented approach. Your generic things will then be structs which each contain a sub-struct (or pointer) to functions which will act as methods. This way it's easy to get generic behavior (although you won't have inheritance or anything like that). Your common interface can then be richer because you can require multiple methods for whatever generic operations you want, without having to try to cram it all into one function. – Paul Hankin Aug 02 '20 at 07:05
  • See this answer https://stackoverflow.com/a/351745/1400793 for examples – Paul Hankin Aug 02 '20 at 07:06
  • I do not understand. What should the result be of the function? Are you writing `asprintf`? `I do not see a way to have unsigned char[]` You are making this function up, so do not use `printf` and write your own tool. `fulfill the goal?` What goal is that? Are writing a serialization function that converts all types to string? I am sure you can find good serialization libraries for C. – KamilCuk Aug 17 '20 at 09:27
  • @Sarahcartnez, I am a little confused what are you trying to achieve here? You already have the args, then, you're trying to write them in a string, why? and the `fmt`, you've to change it each time you change a format or you added a new element in the arg list? so why do that? I am actually more confused, what's the goal here? What do you actuallly want? – reyad Aug 17 '20 at 09:57
  • 1
    The example you showed is just an example of serialization and deserialization of data from/to it's ASCII representation using printf/scanf as a backend (btw, use `asprintf`). As this, I believe your question is too broad - writing serializer for abstract data is a very big job. And, anyway, your `myAddFunc` has to know the types of the data anyway (and misses error checking), so you might as well pass a binary memcpy'ied blob. Use an existing protocol buffer. Json. Protobuf. FlatBuffers. Etc. – KamilCuk Aug 17 '20 at 10:15
  • 1
    @Sarahcartenz, I just saw your update...I understand what you're doing in your code, but my question is why not just pass `(12, 2)` directly in `myAddFunc()`, why bother with creating `struct _common` and using `va_arg` to save those data? What advantage this gives you? You're retrieving `(x, y)`, notice, you've to know `no of elements` and also you've to know `their types`? It does not give you any advantage...without just redundant code... – reyad Aug 17 '20 at 10:15
  • @reyad can you please look at the code update again? Basically I want to provide some services to a user but I want to use one struct for all of them. Again, you can add members to the structs as you see appropriate and so on to remove redundancy – Sarah cartenz Aug 17 '20 at 10:25
  • @Sarahcartenz, okay, let me tell you what I've understood: you've an app. It performs some tasks. Each of the task has an ID, you would use the ID to process the task and each task has different set of processing style. The user of your app, first inputs the ID, then you show him/her a form(a place where one can input some data) where (s)he can input two/three(whatever) numbers/strings(whatever type)...and then you process the task and show output...is it correct? – reyad Aug 17 '20 at 10:35
  • hmm... you just changed the code to something different. And it seems to be buggy. What is `fmt` inside `execute`? – Support Ukraine Aug 17 '20 at 10:38
  • And `infoList` and thereby `list_t` isn't used at all... – Support Ukraine Aug 17 '20 at 10:42
  • 1
    @reyad yess thats kinda the idea. – Sarah cartenz Aug 17 '20 at 10:45
  • @4386427 i put a comment where the code will be placed, basically a search function will be called – Sarah cartenz Aug 17 '20 at 10:45
  • @Sarahcartenz, I guess, if I'm right on the points of what I've told, then, there's way a pretty way to do it....without using standard functions much...you've to use your own parser for that... – reyad Aug 17 '20 at 10:45
  • @Sarahcartenz, using your own parser and type hint would give you a lot of advantages, and it would also provide great readabilty... – reyad Aug 17 '20 at 10:46
  • @Sarahcartenz, if you've problem getting the idea or write code about how to solve using your own type hints and parser, then tell me. I would explain it for you in the answer. But, I can't write it now, I've to do it later(hope you've time)...reply to this comment as soon as you can... – reyad Aug 17 '20 at 10:52
  • @reyad ill search more about it and try it out. but would like to see your solution too, whenever you have time it would be nice to write it in the answers. Thank you – Sarah cartenz Aug 17 '20 at 10:55
  • Infrastructure exists to perform such things. One way is to interface with an embedded script language, like Python or Lua. Another way is to interface with text API interfaces like gSOAP, XML, or CLI Parser. – jxh Aug 17 '20 at 21:18
  • @Sarahcartenz, I've provided a solution...you may read it...and sorry for late reply...and also, tell me if it solves your problem or not... – reyad Aug 18 '20 at 12:02
  • I'm as confused as everyone else seems to be. As I understand it, to use the facility you describe, the user would look up the documentation of the wanted function, pack arguments of the correct type, in the correct order, into an instance of the data structure you are trying to design, and then call the function, passing a pointer to that parameter object. But what is gained by such indirection? Certainly not performance. – John Bollinger Aug 21 '20 at 14:57
  • The kind of thing you describe is sometimes used in application frameworks designed to dynamically adapt to components that are not known specifically in advance, but that's not the use case you seem to have in mind. I think, then, that you're proposing to do a lot of work to make your system slower, more brittle, and harder to use than it otherwise would be. – John Bollinger Aug 21 '20 at 16:15

4 Answers4

1

As I understand your question, you want to save all argument values in a text string so that the values can be reconstructed later using sscanf. Further, you want to be able to handle array-of-number, e.g. an array of unsigned char.

And you ask:

Is there a way to do this

For your idea to work, it's required that sscanf can parse (aka match) the type of data that you want to use in your program. And - as you write in the question - scanf can't parse arrays-of-numbers. So the answer is:

No, it can't be done with standard functions.

So if you want to be able to handle arrays-of-number, you'll have to write your own scan-function. This includes

  1. selection of a conversion specifier to tell the code to scan for an array (e.g. %b),
  2. select a text-format for the array (e.g. "{1, 2, 3}")
  3. a way to store the array-data and the size, e.g. struct {unsiged char* p; size_t nb_elements;}.

Further, you'll have the same problem with vsprintf. Again you need to write your own function.

EDIT

One alternative (that I don't really like myself) is to store pointer values. That is - instead of storing the array values in the string params you can store a pointer to the array.

The upside of that approach is that you can use the standard functions.

The downside is that the caller must ensure that the array exists until execute has been called.

In other words:

unsigned char auc[] = {1, 2, 3};
Vfunc(&c, auc);
execute(&c);

would be fine but

Vfunc(&c, (unsigned char[]){1, 2, 3});
execute(&c);

would compile but fail at run time.

And - as always with arrays in C - you may need an extra argument for the number of array elements.

Example code for this "save as pointer" approach could be:

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

typedef struct _common {
    int ID;
    char *params;
} common;

void Vfunc(common * c, ...) {
    va_list args;
    va_start(args, c);

    //code to search for ID in infoList and fetch its fmt
    // For this example just use a fixed fmt
    char fmt[] ="%p %zu";


    char params_buff[100]; //max params is 100
    vsprintf(params_buff, fmt, args);

    va_end(args);

    c->params = (char *)malloc(sizeof(char)*(strlen(params_buff)+1));
    strncpy(c->params, params_buff, strlen(params_buff)+1);
}

int execute(common * c)
{
    if (c->ID == 1) {
      // expect pointer and number of array elements
      unsigned char* a;
      size_t nbe;

      //code to find ID in infoList and fetch its fmt
      // For this example just use a fixed fmt
      char fmt[] ="%p %zu";

      if (sscanf(c->params, fmt, &a, &nbe) != 2) exit(1);

      // Calculate average
      int sum = 0;
      for (size_t i = 0; i < nbe; ++i) sum += a[i];
      return sum;
    }

    return 0;
}

int main(void)
{
  common c;
  c.ID = 1;

  unsigned char auc[] = {1, 2, 3, 4, 5, 6};

  Vfunc(&c, auc, sizeof auc / sizeof auc[0]);

  printf("The saved params is \"%s\"\n", c.params);
  printf("Sum of array elements are %d\n", execute(&c));

  return 0;
}

Possible Output

The saved params is "0xffffcc0a 6"
Sum of array elements are 21

Notice that it's not the array data that is saved but a pointer value.

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
  • About c.params = "12 {1, 2, 3, 4, 5} 2", what if the user wants to plug in an array coming from another function call they made? this means they need to append their parameters somehow before placing them in c.params ? – Sarah cartenz Aug 17 '20 at 10:59
  • @Sarahcartenz ah, if you want to allow stuff like `Vfunc(&c, some_func_returning_int(), some_func_returning_pointer_to_array_of_numbers());`, you can't use a direct text string as proposed.. BTW: Notice that a function **can not** return an array of numbers - it can only return a pointer to the first element of the array. And you don't know the size... – Support Ukraine Aug 17 '20 at 11:02
  • @Sarahcartenz Have you considered parsing arrays as pointers? – Support Ukraine Aug 17 '20 at 11:09
  • You mean using the implementation in the question? That would require for me to add the size as a member of struct or parameter in the function right? Which i didnt need for char pointers cause i can use the standard strlen for strings – Sarah cartenz Aug 17 '20 at 11:28
  • @Sarahcartenz In your current implementation `c.params` is a text string with all your data. That can't be done using stadard function as array-of-numbers can't be scan'ed. However, what you can do is to store **pointers** to e.g. an array-of-numbers. I don't really like the approach but it can be done with standard function. So if it works for you then it's quite simple... nearly the code you already have – Support Ukraine Aug 17 '20 at 11:34
  • @Sarahcartenz I update the answer with a code example – Support Ukraine Aug 17 '20 at 12:03
  • @Sarahcartenz When you need an array will it always be a fixed number of elements? Like, ID==10 always takes 3 elements and ID=11 always take 7 elements and so on... ? – Support Ukraine Aug 17 '20 at 12:20
  • @yes its fixed, thank you for the example code with the pointer, I'm trying it out for now – Sarah cartenz Aug 17 '20 at 12:22
0

I think what you want from your description is likely a union of structs, with the 1st member of the union being an enumerator that defines the type of the structure in use, which is how polymorphism is often accomplished in C. Look at the X11 headers for a giant example of Xevents

A trivial example:

//define our types
typedef enum  {
chicken,
cow,
no_animals_defined
} animal;

typedef struct {
animal species;
int foo;
char bar[20];
} s_chicken;

typedef struct {
animal species;
double foo;
double chew;
char bar[20];
} s_cow;

typedef union {
animal species; // we need this so the receiving function can identify the type.
s_chicken chicken ;
s_cow cow ;
} s_any_species;

now, this struct may be passed to a function and take on either identity. A receiving function of a type s_any_species may do to de-reference.

void myfunc (s_any_species any_species)
{
if (any_species.species == chicken)
   any_species.chicken.foo=1 ;
}

Arrays of function pointers are preferable to long if else sequences here, but either will work

camelccc
  • 2,847
  • 8
  • 26
  • 52
0

I have read the problem once again, and find out that it's much simpler than you've described.

According to your statement, you already know about the type and order of data retrieval in execute() function. This make this problem much easier.

I must say, this problem is a bit difficult to solve in c, cause c can't resolve type at runtime or dynamically cast type at runtime. c must know all the types before hand i.e. at compile time.

Now, that said, c provides a way to handle variable length arguments. And that's a advantage.

So, what we've to do is:

  1. cache all arguments from variable length arguments i.e. va_list.
  2. and, provide a way to retrieve provided arguments from that cache.

At first, I am going to show you how to retrieve elements from cache if you know the type. We'll do it using a macro. I've named it sarah_next(). Well, after all, I've to write it because of you. You can name it as you want. It's definition is given below:

#define sarah_next(cache, type)                        \
        (((cache) = (cache) + sizeof(type)),           \
        *((type*) (char *) ((cache) - sizeof(type))))

So, in simple words, sarah_next() retrieve the next element from cache and cast it to type.

Now, let's discuss the first problem, where we've to cache all arguments from va_list. You can do it easily by writing as follows:

void *cache = malloc(sizeof(char) * cacheSize);
// itr is an iterator, which iterates over cache
char *itr = (char *)cache;
// now, you can do
*(type *)itr = va_arg(buf, type);
// and then
itr += sizeof(type);

Another, point I would like to discuss is, I've used type hint to determine cache size. For that I've used a function getSize(). You would understand if you just look at it(also note: this gives you the ability to use your own custom type):

// getSize() is a function that returns type size based on type hint
size_t getSize(char type) {
    if(type == 's') {
        return sizeof(char *);
    }
    if(type == 'c') {
        return sizeof(char);
    }
    if(type == 'i') {
        return sizeof(int);
    }
    if(type == 'u') { // 'u' represents 'unsigned char'
        return sizeof(unsigned char);
    }
    if(type == 'x') { // let's, say 'x' represents 'unsigned char *'
        return sizeof(unsigned char *);
    }
    // you can add your own custom type here
    // also note: you can easily use 'unsigned char'
    //            use something like 'u' to represent 'unsigned char'
    //            and you're done
    // if type is not recognized, then
    printf("error: unknown type while trying to retrieve type size\n");
    exit(1);
}

Ok, I guess, the ideas are complete. Before moving on try to grasp the ideas properly.

Now, let me provide the full source code:

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

// note: it is the most fundamental part of this solution
//       'sarah_next' is a macro, that
//       returns *(type *)buf means a value of type "type", and also
//       increments 'buf' by 'sizeof(type)', so that
//       it may target next element
//       'sarah_next' is used to retrieve data from task cache

// I've named it after you, you may choose to name it as you wish
#define sarah_next(cache, type)                        \
        (((cache) = (cache) + sizeof(type)),           \
        *((type*) (char *) ((cache) - sizeof(type))))


// defining pool size for task pool
#define POOL_SIZE 1024


// notice: getSize() has been updated to support unsigned char and unsigned char *
// getSize() is a function that returns type size based on type hint
size_t getSize(char type) {
    if(type == 's') {
        return sizeof(char *);
    }
    if(type == 'c') {
        return sizeof(char);
    }
    if(type == 'i') {
        return sizeof(int);
    }
    if(type == 'u') { // 'u' represents 'unsigned char'
        return sizeof(unsigned char);
    }
    if(type == 'x') { // let's, say 'x' represents 'unsigned char *'
        return sizeof(unsigned char *);
    }
    // you can add your own custom type here
    // also note: you can easily use 'unsigned char'
    //            use something like 'u' to represent 'unsigned char'
    //            and you're done
    // if type is not recognized, then
    printf("error: unknown type while trying to retrieve type size\n");
    exit(1);
}


typedef struct __task {
    int id;
    void *cache;
} Task;

// notice: constructTask has been updated to support unsigned char and unsigned char *
// note: here, types contains type hint
Task *constructTask(int id, char *types, ...) {
    // determine the size of task cache
    int cacheSize = 0;
    for(int i=0; types[i]; i++) {
        cacheSize += getSize(types[i]);
    }
    // allocate memory for task cache
    void *cache = malloc(sizeof(char) * cacheSize);
    
    va_list buf;
    va_start(buf, types);
    
    // itr is an iterator, which iterates over cache
    char *itr = (char *)cache;
    for(int i=0; types[i]; i++) {
        if(types[i] == 's') {
            *(char **)itr = va_arg(buf, char *);

        } else if(types[i] == 'x') { // added support for 'unsigned char *'
            *(unsigned char **)itr = va_arg(buf, unsigned char *);

        } else if(types[i] == 'c') {
            // notice: i used 'int' not 'char'
            // cause: compiler-warning: 'char' is promoted to 'int' when passed through '...'
            // also note: this promotion helps with 'unsigned char'
            *(char *)itr = (char)va_arg(buf, int); // so cast it to char

        } else if(types[i] == 'u') { // added support 'unsigned char'
            // notice: i used 'int' not 'unsigned char'
            // cause: compiler-warning: 'unsigned char' is promoted to 'int' when passed through '...'
            // also note: this promotion helps with 'unsigned char'
            *(unsigned char *)itr = (unsigned char)va_arg(buf, int); // so cast it to unsigned char

        } else if(types[i] == 'i') {
            *(int *)itr = va_arg(buf, int);

        }
        // it won't come to else, cause getSize() would
        // caught the type error first and exit the program
        itr += getSize(types[i]);
    }

    va_end(buf);

    // now, construct task
    Task *task = malloc(sizeof(Task));
    task->id = id;
    task->cache = cache;
    // and return it
    return task;
}

// destroyTask is a function that frees memory of task cache and task
void destroyTask(Task *task) {
    free(task->cache);
    free(task);
}

// notice: that 'task->id == 4' processing part
// it is equivalant to your 'execute()' function
int taskProcessor(Task *task) {
    // define ret i.e. return value
    int ret = 999; // by default it is some code value, that says error

    // note: you already know, what type is required in a task
    if(task->id == 1) {
        // note: see usage of 'sarah_next()'
        int x = sarah_next(task->cache, int);
        int y = sarah_next(task->cache, int);

        ret = x + y;

    } else if(task->id == 2) {
        char *name = sarah_next(task->cache, char *);
        if(strcmp(name, "sarah") == 0) {
            ret = 0; // first name
        } else if (strcmp(name, "cartenz") == 0) {
            ret = 1; // last name
        } else {
            ret = -1; // name not matched
        }
    } else if(task->id == 3) {
        int x = sarah_next(task->cache, int);
        char *name = sarah_next(task->cache, char *);
        int y = sarah_next(task->cache, int);

        printf("%d %s %d\n", x, name, y); // notice: we've been able to retrieve
        // both string(i.e. char *) and int
        // you can also see for ch and int, but i can assure you, it works

        ret = x + y;

    } else if(task->id == 4) { // working with 'unsigned char *'
        int a = sarah_next(task->cache, int);
        unsigned char *x = sarah_next(task->cache, unsigned char *); // cast to unsigned char *
        // char *x = sarah_next(task->cache, char *); // this won't work, would give wrong result
        int b = sarah_next(task->cache, int);

        printf("working with 'unsigned char *':");
        for(int i=0; x[i]; i++) {
            printf(" %d", x[i]); // checking if proper value is returned, that's why using 'integer'
        }
        printf("\n");

        ret = a + b;
    } else {
        printf("task id not recognized\n");
    }

    return ret;
}


int main() {
    Task *taskPool[POOL_SIZE];

    int taskCnt = 0;

    taskPool[taskCnt++] = constructTask(1, "ii", 20, 30); // it would return 50
    taskPool[taskCnt++] = constructTask(1, "ii", 50, 70); // it would return 120
    taskPool[taskCnt++] = constructTask(2, "s", "sarah"); // it would return 0
    taskPool[taskCnt++] = constructTask(2, "s", "cartenz"); // it would return 1
    taskPool[taskCnt++] = constructTask(2, "s", "reyad"); // it would return -1
    taskPool[taskCnt++] = constructTask(3, "isi", 40, "sarah", 60); // it would print [40 sarah 60] and return 100

    // notice: I've added an exmaple to showcase the use of unsigned char *
    // also notice: i'm using value greater than 127, cause
    // in most compiler(those treat char as signed) char supports only upto 127
    unsigned char x[] = {231, 245, 120, 255, 0}; // 0 is for passing 'NULL CHAR' at the end of string
    // 'x' is used to represent 'unsigned char *'
    taskPool[taskCnt++] = constructTask(4, "ixi", 33, x, 789); // it would print ['working with unsigned char *': 231 245 120 255] and return 822
    // note: if you used 'char *' cast to retrieve from 'cache'(using a compiler which treats char as signed), then
    //       it would print [-25 -11 120 -1] instead of [231 245 120 255]
    //       i guess, that makes it clear that you can perfectly use 'unsigned char *'

    for(int i=0; i<taskCnt; i++) {
        printf("task(%d): %d\n", i+1, taskProcessor(taskPool[i]));
        printf("\n");
    }

    // at last destroy all tasks
    for(int i=0; i<taskCnt; i++) {
        destroyTask(taskPool[i]);
    }

    return 0;
}

The output is:

// notice the updated output
task(1): 50                                        
                                                   
task(2): 120                                       
                                                   
task(3): 0                                         
                                                   
task(4): 1                                         
                                                   
task(5): -1                                        
                                                   
40 sarah 60                                        
task(6): 100                                       
                                                   
working with 'unsigned char *': 231 245 120 255    
task(7): 822

So, you may be wondering, what advantage it may create over your given solution. Well, first of all you don't have to use %s %d etc. do determine format, which is not easy to change or create for each task and you've write a lot may be(for each task, you may have to write different fmt), and you don't have use vsprintf etc... which only deals with builtin types.

And the second and great point is, you can use your own custom type. Declare a struct type of your own and you can use. And it is also easy to add new type.

update:

I've forgot to mention another advantage, you can also use unsigned char with it. see, the updated getSize() function. you can use 'u' symbol for unsigned char, and as unsigned char is promoted to int, you can just cast it to (unsigned char) and done...

update-2(support for unsigned char *):

I have updated the code to support unsigned char and unsigned char *. To support new type, the functions, those you need to update, are getSize() and constructTask(). Compare the previous code and the newly updated code...you'll understand how to add new types(you can also add custom types of your own).

Also, take a look at task->id == 4 part in taskProcessor() function. I've added this to showcase the usage of unsigned char *. Hope this clears everything.

If you've any question, then ask me in the comment...

reyad
  • 1,392
  • 2
  • 7
  • 12
  • thank you for the elegant solution reyad! The next macro gave me a lot of insight. With this solution I need to pass the pointer to the unsigned char array if I want an array as a parameter, then again I will need to pass in the size as one of the arguments right like in the first answer? – Sarah cartenz Aug 21 '20 at 13:24
  • Hi, @Sarahcartenz, I've explained and showed how to add support for `unsigned char` and `unsigned char *`. Using this way, you can add support of any `builtin type` and also for your `own custom type`...let me know if you've got the idea... – reyad Aug 21 '20 at 14:23
  • Since 0 is being used to indicate the end of the unsigned char array (NULL), this also means my arrays can't have 0's as values though. But this isnt a problem because i can just pass in the size of the array a parameter to the task as well. Thanks reyad – Sarah cartenz Aug 23 '20 at 10:11
  • @Sarahcartenz, the ascii value of '\0' is neumeric '0'. '\0' is used to indicate termination of string, its how 'c' lang is designed, its not my choice. Its better if you follow the rule in case of string, also notice unsigned does not allow negative values . And yes of course, you can pass the length as a parameter. And you're welcome... – reyad Aug 23 '20 at 11:25
-1

I take you to be asking about conveying a sequence of objects a varying type to a function. As a special detail, you want the function to receive only one actual argument, but this is not particularly significant because it is always possible to convert a function that takes multiple arguments into another that takes only one by wrapping the multiple parameters in a corresponding structure. I furthermore take the example Vfunc() code's usage of vsprintf() to be an implementation detail, as opposed to an essential component of the required solution.

In that case, notwithstanding my grave doubts about the usefulness of what you seem to want, as a C programming exercise it does not appear to be that difficult. The basic idea you seem to be looking for is called a tagged union. It goes by other names, too, but that one matches up well with the relevant C-language concepts and keywords. The central idea is that you define a type that can hold objects of various other types, one at a time, and that carries an additional member that identifies which type each instance currently holds.

For example:

enum tag { TAG_INT, TAG_DOUBLE, TAG_CHAR_PTR };
union tagged {
    struct {
        enum tag tag;
        // no data -- this explicitly gives generic access to the tag
    } as_any;
    struct {
        enum tag tag;
        int data;
    } as_int;
    struct {
        enum tag tag;
        double data;
    } as_double;
    struct {
        enum tag tag;
        char *data;
    } as_char_ptr;
    // etc.
};

You could then combine that with a simple list wrapper:

struct arg_list {
    unsigned num;
    union tagged *args;
};

Then, given a function such as this:

int foo(char *s, double d) {
    char[16] buffer;
    sprintf(buffer, "%15.7e", d);
    return strcmp(s, buffer);
}

You might then wrap it like so:

union tagged foo_wrapper(struct arg_list args) {
    // ... validate argument count and types ...

    return (union tagged) { .as_int = {
        .tag = TAG_INT, .data = foo(args[0].as_char_ptr.data, args[1].as_double.data)
    } };
}

and call the wrapper like so:

void demo_foo_wrapper() {
    union tagged arg_unions[2] = {
        { .as_char_ptr = { .tag = TAG_CHAR_PTR, .data = "0.0000000e+00" },
        { .as_double =   { .tag = TAG_DOUBLE,   .data = 0.0 }
    };
    union tagged result = foo_wrapper((struct arg_list) { .num = 2, .args = arg_unions});
    printf("result: %d\n", result.as_int.data);
}

Update:

I suggested tagged unions because the tags correspond to the field directives in the format strings described in the question, but if they aren't useful to you in practice then they are not an essential detail of this approach. If the called functions will work on the assumption that the caller has packed arguments correctly, and you have no other use for tagging the data with their types, then you can substitute a simpler, plain union for a tagged union:

union varying {
    int as_int;
    double as_double;
    char *as_char_ptr;
    // etc.
};

struct arg_list {
    unsigned num;
    union varying *args;
};

union varying foo_wrapper(struct arg_list args) {
    return (union vaying) { .as_int = foo(args[0].as_char_ptr, args[1].as_double) };
}

void demo_foo_wrapper() {
    union varying arg_unions[2] = {
        .as_char_ptr = "0.0000000e+00",
        .as_double   = 0.0
    };
    union varying result = foo_wrapper((struct arg_list) { .num = 2, .args = arg_unions});
    printf("result: %d\n", result.as_int);
}
John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • think of it a client and a service, the client will pack in the arguments, but without specifying the types. The service will know the types they will receive based on the request. Of course you can have some kind of verification in between. I can see that the user specifies the types in your solution from the wrapper which is not desired? – Sarah cartenz Aug 23 '20 at 08:11
  • @Sarahcartenz, worse and worse. But if you want the called function to work by just assuming the argument types then the approach I've described still works -- just drop the tags. In fact, you can then drop the intermediate structures altogether, and have the `as_double` *etc*. union members refer directly to the data. I'll update this answer momentarily. – John Bollinger Aug 23 '20 at 13:31