7

Is it possible to pass a function and a list of its arguments to another function and call it from inside later on?

void testA(int, float, char* ) {}
void testB(int, float, double ) {} 
void testC(MyClass, float, double ) {} 

template <class T>
void applyA(void(*foo)(void*), std::initializer_list<T> args)
{
    foo(/*unpack args somehow*/);
}

template <class T>
void applyB(void(*foo)(void*), std::initializer_list<T> args)
{
    MyClass cls;
    foo(cls, /*unpack the rest of args*/);
}

int main()
{
    applyA(testA, {5, 0.5f, "abc"});
    applyA(testB, {5, 0.5f, 1.5});
    applyB(testC, {0.5f, 1.5});
}
WrathOfFlame
  • 83
  • 1
  • 7
  • I don't think you can. I'll wait for somebody to prove me wrong!! – R Sahu Feb 07 '18 at 05:39
  • 3
    `std::initializer_list` is, by definition, homogenous - all elements must be of the same type `T`. You are looking for `std::tuple` instead, and moreover, [`std::apply`](http://en.cppreference.com/w/cpp/utility/apply) – Igor Tandetnik Feb 07 '18 at 05:51
  • There's also the issue of your function pointers not matching any of your function prototypes. `void(*)(void*)` is not a "match any function" type. – StoryTeller - Unslander Monica Feb 07 '18 at 05:57
  • You are both right. I feel that std::apply is the way to go, but i'm stick to c++11 standard:( – WrathOfFlame Feb 07 '18 at 06:13
  • 3
    What's wrong with `std::bind` or a lambda? – n. m. could be an AI Feb 07 '18 at 06:36
  • You don't unpack a `int, float, double` into the arguments of `void(*foo)(void*)`. Do you mean "pack a `void (*)(int, float, double )` and a `int, float, double` into an `void(*)(void*)` and a `void *`, such that they get unpacked in the call"? See [here](https://stackoverflow.com/questions/20525977/how-can-i-pass-a-c-lambda-to-a-c-callback-that-expects-a-function-pointer-and) – Caleth Feb 07 '18 at 11:15

2 Answers2

4

You can just forward the arguments, without using arrays, or use tuples like std::apply().

#include <vector>

class MyClass {};

void testA(int, float, const char* ) {}
void testB(int, float, double ) {} 
void testC(MyClass, float, double ) {} 

template <class T, typename... Args>
void applyA(T&& foo, Args... args)
{
    foo(args...);
}

template <class T, typename... Args>
void applyB(T&& foo, Args... args)
{
    MyClass cls;
    foo(cls, args...);
}

int main()
{
    applyA(testA, 5, 0.5f, "abc");
    applyA(testB, 5, 0.5f, 1.5);
    applyB(testC, 0.5f, 1.5);

    return 0;
}

Example with std::apply()

#include <tuple>

...
std::apply(testA, std::make_tuple(5, 0.5f, "abc"));
std::apply(testB, std::make_tuple(5, 0.5f, 1.5));
std::apply(testC, std::make_tuple(MyClass{}, 0.5f, 1.5));

Example with self-made apply ()

With help from "unpacking" a tuple to call a matching function pointer SO question's answer.

template<int ...>
struct seq { };

template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> { };

template<int ...S>
struct gens<0, S...> {
  typedef seq<S...> type;
};


template<typename F, typename Tuple, int... S>
void my_apply_impl(F&& func, Tuple&& params, seq<S...> ) {
    func(std::get<S>(std::forward<Tuple>(params)) ...);
}

template<typename F, typename Tuple>
void my_apply(F&& func, Tuple&& params) {
    my_apply_impl(std::forward<F>(func), std::forward<Tuple>(params), typename gens<std::tuple_size<Tuple>::value>::type() );
}

...
my_apply(testA, std::make_tuple(5, 0.5f, "abc"));
my_apply(testB, std::make_tuple(5, 0.5f, 1.5));
my_apply(testC, std::make_tuple(MyClass{}, 0.5f, 1.5));

Demo

Mihayl
  • 3,821
  • 2
  • 13
  • 32
0

In prev. answer function must be updated to use std::decay, so it can accept const tuples too (updated part below):

template<typename F, typename Tuple>
void my_apply(F&& func, Tuple&& params)
{
    tuples::my_apply_impl(std::forward<F>(func), std::forward<Tuple>(params), typename tuples::gens<std::tuple_size<typename std::decay<Tuple>::type>::value>::type());
}
Alex Zaharov
  • 69
  • 1
  • 3