Это проблема семантической оптимизации, над которой я работаю последние пару дней, и я застрял. Моя настоящая программа работает на RTOS (FreeRTOS, в частности), и мне нужно запускать задачи (которые являются упрощенными, не заканчивающимися версиями потоков). C API принимает void (*)(void*)
для точки ввода задачи и параметр void*
. Довольно стандартный тариф.
Я написал класс-оболочку для задачи и вместо того, чтобы выполнять одну из реализаций старой школы, например, иметь виртуальный метод, который должен быть переопределен конечным классом задачи, я бы предпочел получить С++ для создания необходимого объект-хранилище параметров и функции клея с помощью вариативных шаблонов и функций.
Я уже делал это с lambdas и std::function
и std::bind
, но они, похоже, реализуют некоторое раздувание, а именно, не разрешая целевую функцию до времени выполнения. В принципе, будет использоваться тот же механизм, что и виртуальный метод. Я стараюсь, если возможно, вырезать все накладные расходы. Расцветка выходила примерно на 200 байт на экземпляр больше, чем жестко закодированная реализация. (Это находится на ARM Cortex-M3 со 128-кратной полной вспышкой, и осталось всего около 500 байт.) Все вопросы SO, которые я нашел в этой теме, аналогично откладывают разрешение функции до времени выполнения.
Идея состоит в том, чтобы код:
- Храните разложившиеся версии вариационных аргументов в объекте, выделенном в куче (это упрощение, вместо этого можно использовать Allocator) и передать это как параметр
void*
, - Передайте созданную функцию вызова-острова в качестве точки входа с сигнатурой
void(void*)
, которая вызывает целевую функцию с сохраненными параметрами и - (Это часть, которую я не могу понять), чтобы компилятор выводил типы списка аргументов из сигнатуры целевой функции, чтобы следовать принципу "Не повторять себя".
- Обратите внимание, что указатель функции и ее типы аргументов известны и разрешены во время компиляции, а фактические значения аргумента , переданные функции, неизвестны до времени выполнения (потому что они включают такие вещи, как указатели объектов и параметры конфигурации времени исполнения).
В приведенном ниже примере мне нужно создать экземпляр одной из заданий как Task<void (*)(int), bar, int> task_bar(100);
, когда я бы предпочел написать Task<bar> task_bar(100);
или Task task_bar<bar>(100);
и выяснить, компилятор (или как-то сказать) в библиотеке), что переменные аргументы должны соответствовать списку аргументов указанной функции.
"Очевидным" ответом будет какая-то подпись шаблона, например template<typename... Args, void (*Function)(Args...)>
, но, разумеется, не компилируется. Не так же, как Function
- первый аргумент.
Я не уверен, что это возможно, поэтому я прошу здесь посмотреть, что вы, ребята, придумали. Я пропустил вариантный код, который предназначен для объектных методов вместо статических функций, чтобы упростить вопрос.
Ниже приведен репрезентативный тестовый пример. Я создаю его с помощью gcc 4.7.3 и флага -std=gnu++11
.
#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;
}