“Decompressione” di una tupla per chiamare un puntatore a funzione corrispondente

Sto cercando di memorizzare in una std::tuple un numero variabile di valori, che verrà in seguito utilizzato come argomento per una chiamata a un puntatore a funzione che corrisponde ai tipi memorizzati.

Ho creato un esempio semplificato che mostra il problema che sto cercando di risolvere:

 #include  #include  void f(int a, double b, void* c) { std::cout << a << ":" << b << ":" << c << std::endl; } template  struct save_it_for_later { std::tuple params; void (*func)(Args...); void delayed_dispatch() { // How can I "unpack" params to call func? func(std::get(params), std::get(params), std::get(params)); // But I *really* don't want to write 20 versions of dispatch so I'd rather // write something like: func(params...); // Not legal } }; int main() { int a=666; double b = -1.234; void *c = NULL; save_it_for_later saved = { std::tuple(a,b,c), f}; saved.delayed_dispatch(); } 

Normalmente per problemi che riguardano template std::tuple o variadic scriverei un altro template come template per valutare ricorsivamente tutti i tipi uno per uno, ma non riesco a vedere un modo di fare quello per l’invio di una chiamata di funzione.

La vera motivazione per questo è un po ‘più complessa ed è comunque solo un esercizio di apprendimento. Si può presumere che mi sia consegnata la tupla per contratto da un’altra interfaccia, quindi non può essere modificata ma che il desiderio di decomprimerlo in una chiamata di funzione è mia. Questo esclude l’utilizzo di std::bind come metodo economico per aggirare il problema sottostante.

Qual è un modo pulito per inviare la chiamata usando la std::tuple , o un modo alternativo migliore per ottenere lo stesso risultato netto di memorizzare / inoltrare alcuni valori e un puntatore a funzione fino a un punto futuro arbitrario?

È necessario creare un pacchetto di numeri di parametri e decomprimerli

 template struct seq { }; template struct gens : gens { }; template struct gens<0, S...> { typedef seq type; }; // ... void delayed_dispatch() { callFunc(typename gens::type()); } template void callFunc(seq) { func(std::get(params) ...); } // ... 

Questa è una versione compilabile completa della soluzione di Johanne alla domanda di awoodland, nella speranza che possa essere utile a qualcuno. Questo è stato testato con un’istantanea di g ++ 4.7 su Debian squeeze.

 ################### johannes.cc ################### #include  #include  using std::cout; using std::endl; template struct seq {}; template struct gens : gens {}; template struct gens<0, S...>{ typedef seq type; }; double foo(int x, float y, double z) { return x + y + z; } template  struct save_it_for_later { std::tuple params; double (*func)(Args...); double delayed_dispatch() { return callFunc(typename gens::type()); } template double callFunc(seq) { return func(std::get(params) ...); } }; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wunused-variable" #pragma GCC diagnostic ignored "-Wunused-but-set-variable" int main(void) { gens<10> g; gens<10>::type s; std::tuple t = std::make_tuple(1, 1.2, 5); save_it_for_later saved = {t, foo}; cout << saved.delayed_dispatch() << endl; } #pragma GCC diagnostic pop 

Si può usare il seguente file SConstruct

 ##################### SConstruct ##################### #!/usr/bin/python env = Environment(CXX="g++-4.7", CXXFLAGS="-Wall -Werror -g -O3 -std=c++11") env.Program(target="johannes", source=["johannes.cc"]) 

Sulla mia macchina, questo dà

 g++-4.7 -o johannes.o -c -Wall -Werror -g -O3 -std=c++11 johannes.cc g++-4.7 -o johannes johannes.o 

Ecco una soluzione C ++ 14.

 template  struct save_it_for_later { std::tuple params; void (*func)(Args...); template void call_func(std::index_sequence) { func(std::get(params)...); } void delayed_dispatch() { call_func(std::index_sequence_for{}); } }; 

Questo ha ancora bisogno di una funzione di supporto ( call_func ). Poiché questo è un idioma comune, forse lo standard dovrebbe supportarlo direttamente come std::call con ansible implementazione

 // helper class template class Params, typename... Args, std::size_t... I> R call_helper(std::function const&func, Params const&params, std::index_sequence) { return func(std::get(params)...); } // "return func(params...)" template class Params, typename... Args> R call(std::function const&func, Params const&params) { return call_helper(func,params,std::index_sequence_for{}); } 

Allora il nostro invio ritardato diventa

 template  struct save_it_for_later { std::tuple params; std::function func; void delayed_dispatch() { std::call(func,params); } }; 

La soluzione C ++ 17 è semplicemente quella di usare std::apply :

 auto f = [](int a, double b, std::string c) { std::cout< 

Ho appena sentito che dovrebbe essere dichiarato una volta in una risposta in questo thread (dopo che è già apparso in uno dei commenti).


La soluzione di base C ++ 14 è ancora mancante in questo thread. EDIT: No, è in realtà lì nella risposta di Walter.

Questa funzione è data:

 void f(int a, double b, void* c) { std::cout << a << ":" << b << ":" << c << std::endl; } 

Chiamalo con il seguente frammento:

 template auto call(Function f, Tuple t, std::index_sequence) { return f(std::get(t) ...); } template auto call(Function f, Tuple t) { static constexpr auto size = std::tuple_size::value; return call(f, t, std::make_index_sequence{}); } 

Esempio:

 int main() { std::tuple t; //or std::array t; //or std::pair t; call(f, t); } 

DEMO

Questo è un po ‘complicato da raggiungere (anche se è ansible). Ti consiglio di usare una libreria dove è già implementata, ovvero Boost.Fusion (la funzione invoke ). Come bonus, Boost Fusion funziona anche con i compilatori C ++ 03.

soluzione c ++ 14 . Innanzitutto, alcuni programmi di utilità:

 template auto index_over(std::index_sequence){ return [](auto&&f)->decltype(auto){ return decltype(f)(f)( std::integral_constant{}... ); }; } template auto index_upto(std::integral_constant ={}){ return index_over( std::make_index_sequence{} ); } 

Questi ti permettono di chiamare un lambda con una serie di interi compilabili.

 void delayed_dispatch() { auto indexer = index_upto(); indexer([&](auto...Is){ func(std::get(params)...); }); } 

e abbiamo finito.

index_upto e index_over consentono di lavorare con i pacchetti di parametri senza dover generare un nuovo overload esterno.

Certo, in c ++ 17 tu semplicemente

 void delayed_dispatch() { std::apply( func, params ); } 

Ora, se ci piace, in c ++ 14 possiamo scrivere:

 namespace notstd { template constexpr auto tuple_size_v = std::tuple_size::value; template decltype(auto) apply( F&& f, Tuple&& tup ) { auto indexer = index_upto< tuple_size_v> >(); return indexer( [&](auto...Is)->decltype(auto) { return std::forward(f)( std::get(std::forward(tup))... ); } ); } } 

relativamente facilmente e ottenere la syntax c ++ 17 pronta per la spedizione.

 void delayed_dispatch() { notstd::apply( func, params ); } 

basta sostituire notstd con std quando il tuo compilatore si aggiorna e bob è tuo zio.

Pensando al problema un po ‘di più in base alla risposta data ho trovato un altro modo di risolvere lo stesso problema:

 template  struct call_or_recurse; template  struct dispatcher { template  static void impl(F f, const std::tuple& params, Args... args) { call_or_recurse >::call(f, params, args...); } }; template  struct call_or_recurse { // recurse again template  static void call(F f, const T& t, Args... args) { D::template impl(f, t, std::get(t), args...); } }; template  struct call_or_recurse { // do the call template  static void call(F f, const T&, Args... args) { f(args...); } }; 

Che richiede di cambiare l’implementazione di delayed_dispatch() in:

  void delayed_dispatch() { dispatcher::impl(func, params); } 

Questo funziona convertendo ricorsivamente la std::tuple in un pacchetto di parametri a sé stante. call_or_recurse è necessario come specializzazione per terminare la ricorsione con la chiamata reale, che decomprime il pacchetto di parametri completato.

Non sono sicuro che questa sia in ogni caso una soluzione “migliore”, ma è un altro modo di pensare e risolverlo.


Come un’altra soluzione alternativa, puoi usare enable_if , per creare qualcosa di più semplice della mia precedente soluzione:

 #include  #include  #include  void f(int a, double b, void* c) { std::cout << a << ":" << b << ":" << c << std::endl; } template  struct save_it_for_later { std::tuple params; void (*func)(Args...); template  typename std::enable_if::type delayed_dispatch(Actual&& ...a) { delayed_dispatch(std::forward(a)..., std::get(params)); } void delayed_dispatch(Args ...args) { func(args...); } }; int main() { int a=666; double b = -1.234; void *c = NULL; save_it_for_later saved = { std::tuple(a,b,c), f}; saved.delayed_dispatch(); } 

Il primo sovraccarico prende solo un altro argomento dalla tupla e lo inserisce in un pacchetto di parametri. Il secondo sovraccarico prende un pacchetto di parametri corrispondente e quindi effettua la chiamata reale, con il primo sovraccarico disabilitato nell’unico e solo caso in cui il secondo sarebbe valido.

La mia variante della soluzione di Johannes che utilizzava C ++ 14 std :: index_sequence (e function return type come parametro template RetT):

 template  struct save_it_for_later { RetT (*func)(Args...); std::tuple params; save_it_for_later(RetT (*f)(Args...), std::tuple par) : func { f }, params { par } {} RetT delayed_dispatch() { return callFunc(std::index_sequence_for{}); } template RetT callFunc(std::index_sequence) { return func(std::get(params) ...); } }; double foo(int x, float y, double z) { return x + y + z; } int testTuple(void) { std::tuple t = std::make_tuple(1, 1.2, 5); save_it_for_later saved (&foo, t); cout << saved.delayed_dispatch() << endl; return 0; }