Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to have a pointer to a function with arbitrary arguments as a template parameter?

This is a semantic-optimization problem I've been working on over the past couple days, and I'm stuck. My real program runs on a RTOS (FreeRTOS, specifically), and I need to spawn tasks (which are simplified, non-terminating versions of threads). The C API takes a void (*)(void*) for the task's entry point, and a void* parameter. Pretty standard fare.

I've written a wrapper class for a task, and rather than doing one of the old-school implementations such as having a virtual method that must be overridden by the final task class, I would rather get C++ to generate the necessary parameter-storage object and glue functions by means of variadic templates and functions.

I've done this with lambdas and std::function and std::bind already, but they seem to implement some bloat, namely by not resolving the function target until runtime. Basically the same mechanism as a virtual method would use. I'm trying to cut out all the overhead I can, if possible. The bloat has been coming out to about 200 bytes per instance more than the hard-coded implementation. (This is on an ARM Cortex-M3 with 128K total flash, and we've only got about 500 bytes left.) All of the SO questions I've found on the topic similarly defer resolution of the function until runtime.

The idea is for the code to:

  1. Store the decayed versions of the variadic arguments in an object allocated on the heap (this is a simplification; an Allocator could be used instead), and pass this as the void* parameter,
  2. Pass a generated call-island function as the entry point, with signature void(void*), that calls the target function with the stored parameters, and
  3. (This is the part I can't figure out) have the compiler deduce the types of the argument list from the target function's signature, to follow the Don't Repeat Yourself principle.
  4. Note that the function pointer and its argument types are known and resolved at compile time, and the actual argument values passed to the function are not known until runtime (because they include things like object pointers and runtime configuration options).

In the example below, I have to instantiate one of the tasks as Task<void (*)(int), bar, int> task_bar(100); when I would rather write Task<bar> task_bar(100); or Task task_bar<bar>(100); and have the compiler figure out (or somehow tell it in the library) that the variadic arguments have to match the argument list of the specified function.

The "obvious" answer would be some kind of template signature like template<typename... Args, void (*Function)(Args...)> but, needless to say, that does not compile. Nor does the case where Function is the first argument.

I'm not sure this is even possible, so I'm asking here to see what you guys come up with. I've omitted the variant code that targets object methods instead of static functions in order to simplify the question.

The following is a representative test case. I'm building it with gcc 4.7.3 and the -std=gnu++11 flag.

#include <utility>
#include <iostream>
using namespace std;

void foo() { cout << "foo()\n"; }
void bar(int val) { cout << "bar(" << val << ")\n"; }

template<typename Callable, Callable Target, typename... Args>
struct TaskArgs;

template<typename Callable, Callable Target>
struct TaskArgs<Callable, Target> {
    constexpr TaskArgs() {}
    template<typename... Args>
    void CallFunction(Args&&... args) const
    { Target(std::forward<Args>(args)...); }
};

template<typename Callable, Callable Target, typename ThisArg, 
    typename... Args>
struct TaskArgs<Callable, Target, ThisArg, Args...> {
    typename std::decay<ThisArg>::type arg;
    TaskArgs<Callable, Target, Args...> sub;
    constexpr TaskArgs(ThisArg&& arg_, Args&&... remain)
    : arg(arg_), sub(std::forward<Args>(remain)...) {}
    template<typename... CurrentArgs>
    void CallFunction(CurrentArgs&&... args) const
    { sub.CallFunction(std::forward<CurrentArgs>(args)..., arg); }
};

template<typename Callable, Callable Target, typename... Args>
struct TaskFunction {
    TaskArgs<Callable, Target, Args...> args;
    constexpr TaskFunction(Args&&... args_)
    : args(std::forward<Args>(args_)...) {}
    void operator()() const { args.CallFunction(); }
};

// Would really rather template the constructor instead of the whole class.
// Nothing else in the class is virtual, either.
template<typename Callable, Callable Entry, typename... Args>
class Task {
public:
    typedef TaskFunction<Callable, Entry, Args...> Function;
    Task(Args&&... args): taskEntryPoint(&Exec<Function>), 
        taskParam(new Function(std::forward<Args>(args)...)) { Run(); }
    template<typename Target>
    static void Exec(void* param) { (*static_cast<Target*>(param))(); }
    // RTOS actually calls something like Run() from within the new task.
    void Run() { (*taskEntryPoint)(taskParam); }
private:
    // RTOS actually stores these.
    void (*taskEntryPoint)(void*);
    void* taskParam;
};

int main()
{
    Task<void (*)(), foo> task_foo;
    Task<void (*)(int), bar, int> task_bar(100);
    return 0;
}
like image 687
Mike DeSimone Avatar asked May 26 '13 15:05

Mike DeSimone


1 Answers

Some metaprogramming boilerplate to start:

template<int...> struct seq {};
template<int Min, int Max, int... s> struct make_seq:make_seq<Min, Max-1, Max-1, s...> {};
template<int Min, int... s> struct make_seq<Min, Min, s...> {
  typedef seq<s...> type;
};
template<int Max, int Min=0>
using MakeSeq = typename make_seq<Min, Max>::type;

Helper to unpack a tuple:

#include <tuple>
template<typename Func, Func f, typename Tuple, int... s>
void do_call( seq<s...>, Tuple&& tup ) {
  f( std::get<s>(tup)... );
}

Type of the resulting function pointer:

typedef void(*pvoidary)(void*);

The actual workhorse. Note that no virtual function overhead occurs:

template<typename FuncType, FuncType Func, typename... Args>
std::tuple<pvoidary, std::tuple<Args...>*> make_task( Args&&... args ) {
  typedef std::tuple<Args...> pack;
  pack* pvoid = new pack( std::forward<Args>(args)... );
  return std::make_tuple(
    [](void* pdata)->void {
      pack* ppack = reinterpret_cast<pack*>(pdata);
      do_call<FuncType, Func>( MakeSeq<sizeof...(Args)>(), *ppack );
    },
    pvoid
  );
}

Here is a macro that removes some decltype boilerplate. In C++17 (and maybe 14) this shouldn't be required, we can deduce the first argument from the second:

#define MAKE_TASK( FUNC ) make_task< typename std::decay<decltype(FUNC)>::type, FUNC >

Test harness:

#include <iostream>

void test( int x ) {
  std::cout << "X:" << x << "\n";
}
void test2( std::string s ) {
  std::cout << "S:" << s.c_str() << "\n";
}
int main() {
  auto task = MAKE_TASK(test)( 7 );
  pvoidary pFunc;
  void* pVoid;
  std::tie(pFunc, pVoid) = task;
  pFunc(pVoid);
  delete std::get<1>(task); // cleanup of the "void*"
  auto task2 = MAKE_TASK(test2)("hello");
  std::tie(pFunc, pVoid) = task2;
  pFunc(pVoid);
  delete std::get<1>(task2); // cleanup of the "void*"
}

Live version

And, for posterity, my first stab, which is fun, but missed the mark: Old version (It does run-time binding of the function to call, resulting in calls to the voidary function doing two calls unavoidably)

One minor gotcha -- if you don't std::move the arguments into the task (or otherwise induce a move on that call, like using temporaries), you'll end up with references to them rather than copies of them in the void*.

like image 95
Yakk - Adam Nevraumont Avatar answered Oct 21 '22 20:10

Yakk - Adam Nevraumont