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:
- 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, - Pass a generated call-island function as the entry point, with signature
void(void*)
, that calls the target function with the stored parameters, and - (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.
- 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;
}