Maksim Melnik
Maksim Melnik

Reputation: 555

C++ generic function call with varargs parameter

In my project i have functions with different number of input parameters with different types. Since these functions are parts of libraries, I cannot change their definitions or bodies.

void methodA(boolean p1, int p2, long p3){
    ... some unrelevant code here ...
}

void methodB(int p1, int p2, int p3, long p4){
    ... some unrelevant code here too ...
}

int methodC(long p4){
    ...
}

In my project i need to have a method, which would receive the address of one of these functions. Additionally it receives well-formed list of parameters (which fits the function in the first parameter). Then this method has to call the passed function with the passed parameters.

Here is what I have now: (i have simplified the code a bit to make my idea clear)

void intercaller(void* some_func_address, ...){

    // VARARGS parameters extractor
    va_list listPointer;
    va_start( listPointer, some_func_address );

    int p1 = va_arg( listPointer, int );
    int p2 = va_arg( listPointer, int );
    int p3 = va_arg( listPointer, int );
    long p4 = va_arg( listPointer, long );

    // TODO: THIS IS NOT GENERIC CALL , CANN ONLY CALL METHOD B
    ((void (*)( int , int , int , long )) some_func_address)( p1 , p2 , p3 , p4 );

    va_end( listPointer );
}

My problem is the actual function call. The parameter list in the function call should be generic and should be able to include different number of parameters, sadly i dont know how to do that... I have tried passing varargs list like here:

((void (*)( va_list )) some_func_address)( listPointer);

but this messes up the parameters in the called function...

So my question is: is there a way to call a given function with given parameters in a generic manner? Maybe I need some sort of a typedeff or a wrapper function?

Upvotes: 4

Views: 2466

Answers (4)

Aconcagua
Aconcagua

Reputation: 25526

Now coming from your other question, what about this:

(Side note: referenced question tells (in the comments) the void* pointers are coming from some custom map, so there shouldn't be – as far as I can see – any issue with replacing them by other appropriate pointers/classes – which I am going to do...)

#include <stdarg.h>

class FunctionWrapper
{
public:
    virtual ~FunctionWrapper() { }
    virtual void operator()(va_list&) = 0;
};

template<typename Result, typename ... Parameters>
class FWrapper : public FunctionWrapper
{
    Result (*mFunction)(Parameters...);
    template <typename T>
    T extract(va_list& list)
    {
        return va_arg(list, T);
    }
public:
    FWrapper(Result (*function)(Parameters...))
            : mFunction(function)
    { }
    virtual void operator()(va_list& list)
    {
        static_cast<void>(mFunction(extract<Parameters>(list)...));
    }
};

// facilitates creating the wrappers:
template<typename Result, typename ... Parameters>
FunctionWrapper* createWrapper(Result (*function)(Parameters...))
{
    return new FWrapper<Result, Parameters ...>(function);
}

void f1(int x, int y)
{
    std::cout << x << ' ' << y << std::endl;
}

void f2(double x, double y)
{
    std::cout << x << ' ' << y << std::endl;
}

// e. g.:
FunctionWrapper* gWrappers[] = { createWrapper(&f1), createWrapper(&f2) };
// from your other question: you'd fill the wrappers into the map you mentioned there:
// map[whatever] = createWrapper(&function);

void interceptor(FunctionWrapper* wrapper, ...)
{
    va_list list;
    va_start(list, wrapper);
    (*wrapper)(list);
    va_end(list);
}

int main(int argc, char* argv[])
{
    interceptor(gWrappers[0], 7, 7);
    interceptor(gWrappers[1], 10.12, 12.10);

    return 0;
}

This solves the issue via polymorphism: A function wrapper class template class (we need a non-template base class to be able to place all the template instances into an array or a map; this is what your original – but actually illegal – void* pointer served for), resolving the va_list into arguments and calling the original function with...

Upvotes: 1

Aconcagua
Aconcagua

Reputation: 25526

Would this help you out?

#include <stdarg.h>

template <typename T>
T extract(va_list& list)
{
    return va_arg(list, T);
}

template<typename Result, typename ... Parameters>
Result call(Result(*function)(Parameters...), va_list& list)
{
    return function(extract<Parameters>(list)...);
}

void f1(int x, int y)
{
    std::cout << x << ' ' << y << std::endl;
}

void f2(double x, double y)
{
    std::cout << x << ' ' << y << std::endl;
}

void interceptor(void* f, ...)
{
    va_list list;
    va_start(list, f);
    if(f == &f1)
    {
        call(f1, list);
    }
    else if(f == f2)
    {
        call(f2, list);
    }
    va_end(list);
}

int main(int argc, char* argv[])
{
    interceptor((void*)&f1, 7, 7);
    interceptor((void*)&f2, 10.12, 12.10);
    return 0;
}

I personally would yet prefer pasing an enum representing the functions to the interceptor function instead of the void* pointer and using switch/case inside.

If you can make the interceptor a template function, it gets even much easier (drop the call template function entirely):

template<typename Result, typename ... Parameters>
void interceptor(Result(*function)(Parameters...), ...)
{
    va_list list;
    va_start(list, function);
    function(extract<Parameters>(list)...);
    va_end(list);
}

int main(int argc, char* argv[])
{
    interceptor(&f1, 7, 7);
    interceptor(&f2, 10.12, 12.10);
    return 0;
}

Upvotes: 1

Not a real meerkat
Not a real meerkat

Reputation: 5729

If you don't have std::invoke yet, use variadic templates. To treat void functions nicely, use SFINAE.

template<typename R, typename... Args>
auto call(R(*function)(Args...), Args... args) -> typename std::enable_if<!std::is_same<R, void>::value, R>::type {
    return function(args...);
}

template<typename... Args>
void call(void (*function)(Args...), Args... args) {
    function(args...);
}

Example:

void a() {
    std::cout << 'a';
}

void b(int a) {
    std::cout << "b:" << a;
}

int c(int a) {
    return a;
}

int main() {
    call(a);
    call(b, 1);
    std::cout << "c:" << call(c, 2);
}

Don't forget to #include <type_traits> for std::enable_if and std::is_same.

Try it online!

Upvotes: 3

DontPanic
DontPanic

Reputation: 2406

va_args are still somewhat black magic to me, but I believe the second arg to va_start should be the first arg to the called function. I don't understand what your "clazz" is. I believe you you should call va_start as:

va_start( listpointer, some_func_address ); 

instead of:

va_start( listPointer, clazz );

Upvotes: 1

Related Questions