Jump to content

How to achieve this?

Gat Pelsinger

In C, I have this function - 

 

static inline const bool check(void (*funcPtr1)(va_list), void (*funcPtr2)(va_list), ...)
{
    
} 

 

Now here, I need to be able to pass it other arguments of different data types, so that the it can pass those to the function pointers. So what is the problem? I already set it as a variadic function. Well the problem is that I need to be able to differentiate between the arguments received for both the functions. I can use this by injecting a sentinel value like NULL or something, which will indicate me the end of the arguments of one function, but, because I need to call the functions with all their parameters in one go, I need to have a place where I can store the arguments until I reach the sentinel value. How would I do that? Also, I guess there might be a way by using struct arrays, but I don't have a clear view.

Microsoft owns my soul.

 

Also, Dell is evil, but HP kinda nice.

Link to comment
Share on other sites

Link to post
Share on other sites

I guess you could do something like this?

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


typedef double (*FunctionPtr)(int, va_list);

bool check_always_increasing(int num_functions, ...) {
    va_list args;
    va_start(args, num_functions);
    
    double last_value = 0;
    for (int i = 0; i < num_functions; ++i) {
        FunctionPtr func = va_arg(args, FunctionPtr);
        int arg = va_arg(args, int);
        double current_value = (double)func(arg, args); // Pass the remaining arguments to the function
        if (current_value <= last_value) {
            va_end(args);
            return false;
        }
        last_value = current_value;
        
    }

    va_end(args);
    return true;
}

int sum(int num_args, va_list args) {
    
    int sum = 0;
    for (int i = 0; i < num_args; i++) {
        sum += va_arg(args, int);
    }
    
    printf("sum = %d\n", sum);
    return sum;
}

double mult(int num_args, va_list args) {
    
    double product = 1;
    for (int i = 0; i < num_args; i++) {
        product *= va_arg(args, double);
    }
    
    printf("mult = %f\n", product);
    return product;
}

int main() {
    bool is_increasing = check_always_increasing(2, sum, 2, 5, 10, mult, 3, 3.14, 5.0, 4.1);
    
    printf("Function outputs always increasing? %s\n", is_increasing ? "true" : "false");
    return 0;
}

Seems like a bad idea though. You're losing type safety and making the code much harder to maintain and much more error prone. Plus I'm not sure you'll get consistent behavior between different architectures/compilers. Casting functions into function pointers with inconsistent signatures is not a defined behavior, so the implementation can vary from system to system and compiler to compiler.

Link to comment
Share on other sites

Link to post
Share on other sites

@QuantumRand You didn't have to write a whole program, just a function. Well the problem is, I don't understand a single thing of that loop in the check function. This is some code logic that I have never come across, so I need an easier explanation. Also, my check function is going to receive arguments of different data types, but here you have fixed it to an int?

Microsoft owns my soul.

 

Also, Dell is evil, but HP kinda nice.

Link to comment
Share on other sites

Link to post
Share on other sites

I'll add more comments.

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

// Define our function pointer type. 
// Takes an int declaring how many arguments are passed.
// And a va_list which holds those arguments.
typedef double (*FunctionPtr)(int, va_list);

// Our variadic function. Takes an int declaring how many functions are passed in.
// ... will be a combination of FunctionPtrs and arguments for those functions.
bool check_always_increasing(int num_functions, ...) {
    va_list args;
    
    // Start iterating over the arguments
    va_start(args, num_functions);
    
    double last_value = 0;
  
    // Iterate over each function. As long as arguments passed in are correct, this will work.
    // This relies on each function called to also iterate over the correct number of arguments.
    for (int i = 0; i < num_functions; ++i) {
        // Get our function that we want to call
        FunctionPtr func = va_arg(args, FunctionPtr);
      
        // get the next argument which should be the number of arguments for func above
        int num_of_args = va_arg(args, int);
      
        // Call func and pass in args which now points to the first of num_of_args arguments for func.
        double current_value = (double)func(num_of_args, args);
      
        if (current_value <= last_value) {
            va_end(args);
            return false;
        }
        last_value = current_value;
        
    }

    // Done iterating over args
    va_end(args);
    return true;
}

// Takes an int declaring the number of args in va_list args.
// args has already been loaded by check_always_increasing already calling va_start.
int sum(int num_args, va_list args) {
    
    int sum = 0;
  
    // Hopefually num_args is correct or well start getting arguments for the next function
    for (int i = 0; i < num_args; i++) {
        // va_args(args, int) will pop an int off of the args stack. Hopefully we passed in ints for those...
        sum += va_arg(args, int);
    }
    
    printf("sum = %d\n", sum);
    return sum;
}

// Takes an int declaring the number of args in va_list args
double mult(int num_args, va_list args) {
    
    double product = 1;
  
    // Iterate over args. Hopefully num_args is correct
    for (int i = 0; i < num_args; i++) {
        // pop the next double off of args stack...hopefully we passed in a double there.
        product *= va_arg(args, double);
    }
    
    printf("mult = %f\n", product);
    return product;
}

int main() {
    // passing in 2 functions (sum and mult).
    // sum has 2 arguments (5 and 10). These are ints.
    // mult has 3 arguments (3.14, 5.0, and 4.1). These are doubles.
    bool is_increasing = check_always_increasing(2, sum, 2, 5, 10, mult, 3, 3.14, 5.0, 4.1);
    
    printf("Function outputs always increasing? %s\n", is_increasing ? "true" : "false");
    return 0;
}

Basically, the variadic function check_always_increasing will take arbitrary arguments which get accessed through the va_list args. Then, args gets passed around in a delicate dance where an int gets pulled out which tells you how many more arguments to pull out of it.

 

This is not a good pattern to use in your code. Nothing is enforcing type safety here since you need to pull data out of a va_list and essentially cast it to the type you expect. The moment the wrong type or value gets passed into your variadic function, everything will break. You'll get no errors or warning during compile time.

 

Further, as I hinted at before, casting functions into function pointers is an undefined behavior, meaning this can be implemented in different ways depending on your compiler, architecture, and environment. This might work fine on one system, but not another.

Link to comment
Share on other sites

Link to post
Share on other sites

@QuantumRand

 

ChatGPT provided me this code. I will write only the the important part.

 

struct FunctionArgument {
    void (*funcPtr)(va_list);
    va_list args;
};

// Define a sentinel value for marking the end of arguments
#define END_ARGUMENTS NULL

// Function to call the function pointers with their respective arguments
static inline const bool check(struct FunctionArgument *args) {
    // Iterate through the array of FunctionArguments until we reach the sentinel value
    for (int i = 0; args[i].funcPtr != END_ARGUMENTS; i++) {
        // Call the function pointer with its arguments
        args[i].funcPtr(args[i].args);
        // Clean up the va_list
        va_end(args[i].args);
    }
    return true;
}

 

So, we make a struct which contains the function pointer and its arguments to pass, and we pass them directly in the function. And in main, to call it, we do something like - 

 

  va_list args1, args2;
    va_start(args1, NULL); // Start va_list for func1
    va_arg(args1, int);    // Push arguments for func1
    va_arg(args1, double);
    va_start(args2, NULL); // Start va_list for func2
    va_arg(args2, char*);  // Push arguments for func2
    va_arg(args2, int);

    // Create an array of FunctionArguments to store function pointers and arguments
    struct FunctionArgument arguments[] = {
        { func1, args1 },
        { func2, args2 },
        { END_ARGUMENTS, } // Sentinel value to mark the end of arguments
    };

 

Does this work, and is this efficient?

 

EDIT - oh now wait, the main part should be a little different according to my needs. Here we create va_lists in the main function, but can I directly pass values as variadic values?

Microsoft owns my soul.

 

Also, Dell is evil, but HP kinda nice.

Link to comment
Share on other sites

Link to post
Share on other sites

@QuantumRand

 

I am supreme level dumb. Something like this is just straightaway possible - 

check(my_strlen("Hello"), my_strlen2("Hello"));

We can give the arguments on the spot. But, I would still like to find ways to do like given above but that is for a later day. Right now, this is possible and I am happy.

Microsoft owns my soul.

 

Also, Dell is evil, but HP kinda nice.

Link to comment
Share on other sites

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×