Home > Software engineering >  How can I pass a function name and variadic list of arguments to a function in C ?
How can I pass a function name and variadic list of arguments to a function in C ?

Time:09-30

Not a duplicate of What is std::invoke in c ?. That quesiton asks specifically about that one and only feature. This question asks about a concept which can be 100% solved withOUT that feature, and has multiple, alternative solutions, only some of which even use that feature.


In Python you can pass a function name and arguments list to an outer function, which calls the inner function and passes those args to it, like this:

Passing functions with arguments to another function in Python?:

def perform(fun, *args):
    fun(*args)

def action1(args):
    # something

def action2(args):
    # something

perform(action1)
perform(action2, p)
perform(action3, p, r)

How do I do this in C ?

CodePudding user response:

Like this:

template <typename F, typename ...P>
void foo(F &&func, P &&... args)
{
    std::invoke(std::forward<F>(func), std::forward<P>(args)...);
}

If the function is called more than once, only the last call is allowed to use std::forward. The other calls have to be just std::invoke(func, args...).

You can get rid of std::invoke and use std::forward<F>(func)(std::forward<P>(args)...) and func(args...) respectively, but by doing this you lose support for member pointers as "functions".


To return the result of the function call, just do return std::invoke(...), and set the return type to decltype(auto) or std::invoke_result_t<F, P...>.

This works even when passing a function returning void.


If you want to make this SFINAE-friendly, you can use std::invocable:

template <typename F, typename ...P>
void foo(F &&func, P &&... args) requires std::invocable<F, P...>
{
    std::invoke(std::forward<F>(func), std::forward<P>(args)...);
}

If the function is also invoked without forward, add && std::invocable<F &, P &...>.


To get the correct exception specification, add noexcept(std::is_nothrow_invocable_v<F, P...>) (and if the function is also invoked without forward, also check with <F &, P &...>).

CodePudding user response:

How to use variadic templates (parameter packs) in C to pass a variadic list of arguments to a sub-function

Although you can do this in both C and C using variadic functions with va_list, va_start(), va_arg(), and va_end(), it is much cleaner and easier to do it in C using variadic templates (parameter packs) instead.

The secret is to allow a generic function to be passed in, of any format, via typename FuncType, and to allow a variadic list of arguments to be passed in via typename... FuncArgs. The template specifier will therefore be template<typename FuncType, typename... FuncArgs>. You then pass the function name to the outer function as FuncType innerFunc, and you pass the list of variadic arguments to the outer function as FuncArgs... args. Inside the template function, the list of arguments can then be passed to a subfunction as args..., like this: innerFunc(args...);.

Here is the whole thing in context:

// INNER FUNCTIONS TO PASS TO AN OUTER FUNCTION

void print1(int i)
{
    printf("print1: %i\n", i);
}

void print2(double d, int i)
{
    printf("print2: %f, %i\n", d, i);
}

void print3(int i, double d, const std::string& str)
{
    printf("print3: %i, %f, %s\n", i, d, str.c_str());
}


// OUTER FUNCTION

template<typename FuncType, typename... FuncArgs>
void OuterFunc(FuncType innerFunc, FuncArgs... args)
{
    printf("OuterFunc start.\n");

    // Call the inner function with all passed-in args!
    printf("Calling inner function with all passed-in args.\n");
    innerFunc(args...);

    printf("OuterFunc end.\n\n");
}

int main()
{
    OuterFunc(print1, 100);
    OuterFunc(print2, 99.1234, 77);
    OuterFunc(print3, 123, 10.55, "hey you!");

    return 0;
}

Full, runnable example, with comments:

variadic_templates_parameter_packs_and_functions.cpp from my eRCaGuy_hello_world repo:

// C   includes
#include <cstdint>  // For `uint8_t`, `int8_t`, etc.
#include <cstdio>   // For `printf()`
#include <iostream>  // For `std::cin`, `std::cout`, `std::endl`, etc.
#include <string>


// -------------------- Some inner functions to choose from START -------------------

void print1(int i)
{
    printf("print1: %i\n", i);
}

void print2(double d, int i)
{
    printf("print2: %f, %i\n", d, i);
}

void print3(int i, double d, const std::string& str)
{
    printf("print3: %i, %f, %s\n", i, d, str.c_str());
}

// -------------------- Some inner functions to choose from END ---------------------

// The outer function, which is a variadic template, containing one `typename...` parameter pack.
// See: https://en.cppreference.com/w/cpp/language/parameter_pack
template<typename FuncType, typename... FuncArgs>
void OuterFunc(FuncType innerFunc, FuncArgs... args)
{
    printf("OuterFunc start.\n");

    // Call the inner function with all passed-in args!
    printf("Calling inner function with all passed-in args.\n");
    // See the "Expansion loci" section of this documentation here:
    // https://en.cppreference.com/w/cpp/language/parameter_pack
    // This is really cool, because calling the inner function like this is **just like the Python
    // example here!**: https://stackoverflow.com/a/803632/4561887--except you pass the arguments
    // to the inner function as `args...` in C   here instead of as `*args` (the contents of the
    // arguments list) in Python.
    innerFunc(args...);

    printf("OuterFunc end.\n\n");
}

// int main(int argc, char *argv[])  // alternative prototype
int main()
{
    printf("Demonstrate variadic templates (parameter packs) in C  !\n\n");

    OuterFunc(print1, 100);
    OuterFunc(print2, 99.1234, 77);
    OuterFunc(print3, 123, 10.55, "hey you!");

    return 0;
}

Sample build and run command, and output:

eRCaGuy_hello_world/cpp$ time g   -Wall -Wextra -Werror -O3 -std=c  17 variadic_templates_parameter_packs_and_functions.cpp -o bin/a && bin/a

real    0m0.281s
user    0m0.245s
sys 0m0.036s
Demonstrate variadic templates (parameter packs) in C  !

OuterFunc start.
Calling inner function with all passed-in args.
print1: 100
OuterFunc end.

OuterFunc start.
Calling inner function with all passed-in args.
print2: 99.123400, 77
OuterFunc end.

OuterFunc start.
Calling inner function with all passed-in args.
print3: 123, 10.550000, hey you!
OuterFunc end.

CodePudding user response:

I think we can create template class to pass function pointer. Here is my example to call both free function and class method.

template<typename T>
class Invoker;

template<class C, typename Ret, typename... Args>
class Invoker<Ret (C::*)(Args...)>
{
public:
    Ret Invoke(Ret(C::*method)(Args...), C* instance, Args... args) {
        return std::invoke(method, instance, std::forward<Args>(args)...);
    }
};

template<typename Ret, typename... Args>
class Invoker<Ret(Args...)>
{
public:
    Ret Invoke(Ret(method)(Args...), Args... args) {
        return std::invoke(method, std::forward<Args>(args)...);
    }
};

// use



void foo(int a) {
    cout << __FUNCTION__  << a << endl;
}

int bar() {
    cout << __FUNCTION__ << endl;
    return 42;
}

class Foo {
public:
    void foo(int a) {
        cout << __FUNCTION__  << a << endl;
    }
};



int main()
{
    Foo f;
    Invoker<decltype(&Foo::foo)> inv;
    inv.Invoke(&Foo::foo, &f, 10);

    Invoker<decltype(foo)> inv2;
    inv2.Invoke(&foo, 5);

    Invoker<decltype(bar)> inv3;
    inv3.Invoke(&bar);    
    
    return 0;
}
  • Related