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;
}