Questo è un problema di ottimizzazione semantica su cui ho lavorato negli ultimi due giorni e sono bloccato. Il mio vero programma gira su un RTOS (FreeRTOS, in particolare), e ho bisogno di generare attività (che sono versioni semplificate e non terminanti di thread). L'API C prende uno void (*)(void*)
per il punto di ingresso dell'attività e un parametro void*
. Abbastanza standard.Come avere un puntatore a una funzione con argomenti arbitrari come parametro di modello?
Ho scritto una classe wrapper per un'attività e piuttosto che eseguire una delle implementazioni della vecchia scuola come avere un metodo virtuale che deve essere sovrascritto dalla classe task finale, preferirei che C++ generasse il le necessarie funzioni di memorizzazione dei parametri e di colla tramite modelli e funzioni variadici.
Ho fatto questo con lambdas e std::function
e std::bind
già, ma sembrano implementare un po 'gonfio, vale a dire non risolvendo la funzione obiettivo fino al runtime. In pratica, lo stesso meccanismo utilizzato da un metodo virtuale. Sto cercando di tagliare tutto il sovraccarico che posso, se possibile. Il bloat è arrivato a circa 200 byte per istanza in più rispetto all'implementazione codificata. (Questo è su un ARM Cortex-M3 con 128K di flash totale, e abbiamo solo circa 500 byte rimasti.) Tutte le domande SO che ho trovato sull'argomento rimandano in modo simile la risoluzione della funzione fino al runtime.
L'idea è che il codice:
- Conservare le versioni degradate degli argomenti variadic in un oggetto allocato sul mucchio (questa è una semplificazione, un allocatore potrebbe essere utilizzato invece), e passare questo come parametro
void*
, - passare una funzione chiamata isola generata come punto di ingresso, con la firma
void(void*)
, che chiama la funzione di destinazione con i parametri memorizzati, e - (questa è la parte che non riesco a capire) fare in modo che il compilatore deduca i tipi dell'elenco di argomenti dal targe La firma della funzione t, per seguire il principio Non ripetersi.
- Nota che il puntatore a funzione e dei suoi tipi di argomento sono noti e risolti al momento della compilazione, e l'attuale valori degli argomenti passati alla funzione non sono noti fino al runtime (perché includono cose come puntatori di oggetti e opzioni di configurazione runtime).
Nell'esempio che segue, devo istanziare uno dei compiti come Task<void (*)(int), bar, int> task_bar(100);
quando avrei preferito scrivere Task<bar> task_bar(100);
o Task task_bar<bar>(100);
e avere la figura del compilatore out (o in qualche modo raccontarla in biblioteca) che gli argomenti variadic deve corrispondere alla lista degli argomenti della funzione specificata.
La risposta "ovvia" sarebbe una sorta di firma modello come template<typename... Args, void (*Function)(Args...)>
ma, inutile dirlo, che non viene compilata. Né lo è il caso in cui Function
è il primo argomento.
Non sono sicuro che questo sia possibile, quindi sto chiedendo qui per vedere cosa ne pensate voi. Ho omesso il codice della variante che indirizza i metodi oggetto invece delle funzioni statiche per semplificare la domanda.
Quanto segue è un caso di prova rappresentativo. Lo sto costruendo con gcc 4.7.3 e il flag -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;
}
L'unica cosa che si può fare è 'Task' con la configurazione corrente. La mia domanda è, però - perché il puntatore a funzione ha un parametro di modello? Se ti sbarazzi di questo requisito, puoi semplicemente "auto tfoo = make_task (foo);" con deduzione completa. –
Xeo
Si consiglia di guardare [qui] (http://stackoverflow.com/a/9045644/775806). –
Perché usare i modelli quando typedef e un param faranno? –