Funzione passata come argomento del modello

Sto cercando le regole che implicano il passaggio di funzioni di template C ++ come argomenti.

Questo è supportato da C ++ come mostrato da un esempio qui:

#include  void add1(int &v) { v+=1; } void add2(int &v) { v+=2; } template  void doOperation() { int temp=0; T(temp); std::cout << "Result is " << temp << std::endl; } int main() { doOperation(); doOperation(); } 

Tuttavia, conoscere questa tecnica è difficile. Googling per “funzione come argomento modello” non porta a molto. E i classici modelli C ++ La guida completa sorprendentemente anche non ne discute (almeno non dalla mia ricerca).

Le domande che ho sono se questo è valido C ++ (o solo alcune estensioni ampiamente supportate).

Inoltre, c’è un modo per consentire a un funtore con la stessa firma di essere usato in modo intercambiabile con funzioni esplicite durante questo tipo di invocazione del modello?

Quanto segue non funziona nel programma sopra, almeno in Visual C ++ , perché la syntax è ovviamente sbagliata. Sarebbe bello poter distriggersre una funzione per un functor e viceversa, in modo simile al modo in cui è ansible passare un puntatore o un functor alla funzione std :: sort se si desidera definire un’operazione di confronto personalizzata.

  struct add3 { void operator() (int &v) {v+=3;} }; ... doOperation(); 

I puntatori a un collegamento web o due o una pagina nel libro Modelli C ++ sarebbero apprezzati!

Sì, è valido.

Per quanto riguarda il fatto di farlo funzionare anche con i funtori, la solita soluzione è simile a questo:

 template  void doOperation(F f) { int temp=0; f(temp); std::cout << "Result is " << temp << std::endl; } 

che ora può essere chiamato come uno:

 doOperation(add2); doOperation(add3()); 

Il problema con questo è che se rende complicato il compilatore per inline la chiamata a add2 , poiché tutto il compilatore sa è che un puntatore di funzione di tipo void (*)(int &) viene passato a doOperation . (Ma add3 , essendo un functor, può essere facilmente integrato. Qui, il compilatore sa che un object di tipo add3 viene passato alla funzione, il che significa che la funzione da chiamare è add3::operator() , e non solo alcuni sconosciuti puntatore di funzione.)

I parametri del modello possono essere parametrizzati per tipo (typename T) o per value (int X).

Il metodo “tradizionale” di C ++ per la creazione di un codice è quello di utilizzare un funtore, ovvero il codice si trova in un object e l’object fornisce quindi il tipo univoco del codice.

Quando si lavora con le funzioni tradizionali, questa tecnica non funziona bene, perché un cambiamento nel tipo non indica una funzione specifica , ma specifica solo la firma di molte funzioni possibili. Così:

 template int do_op(int a, int b, OP op) { return op(a,b); } int add(int a, int b) { return a + b; } ... int c = do_op(4,5,add); 

Non è equivalente al caso del funtore. In questo esempio, do_op viene istanziato per tutti i puntatori di funzione la cui firma è int X (int, int). Il compilatore dovrebbe essere piuttosto aggressivo per integrare completamente questo caso. (Non lo escluderei comunque, dato che l’ottimizzazione del compilatore è diventata abbastanza avanzata).

Un modo per dire che questo codice non fa esattamente ciò che vogliamo è:

 int (* func_ptr)(int, int) = add; int c = do_op(4,5,func_ptr); 

è ancora legale, e chiaramente questo non si inarca. Per ottenere l’inlining completo, abbiamo bisogno di template per valore, quindi la funzione è completamente disponibile nel template.

 typedef int(*binary_int_op)(int, int); // signature for all valid template params template int do_op(int a, int b) { return op(a,b); } int add(int a, int b) { return a + b; } ... int c = do_op(4,5); 

In questo caso, ogni versione istanziata di do_op viene istanziata con una funzione specifica già disponibile. Quindi ci aspettiamo che il codice per do_op assomigli molto a “return a + b”. (Programmatori Lisp, smettila di sorridere!)

Possiamo anche confermare che questo è più vicino a ciò che vogliamo perché questo:

 int (* func_ptr)(int,int) = add; int c = do_op(4,5); 

non riuscirà a compilare. GCC dice: “errore: ‘func_ptr’ non può apparire in un’espressione-costante. In altre parole, non posso espandere completamente do_op perché non mi hai dato abbastanza informazioni al momento del compilatore per sapere qual è il nostro op.

Quindi, se il secondo esempio è davvero completamente in linea con il nostro op, e il primo non lo è, a che serve il modello? Cosa sta facendo? La risposta è: tipo coercizione. Questo riff sul primo esempio funzionerà:

 template int do_op(int a, int b, OP op) { return op(a,b); } float fadd(float a, float b) { return a+b; } ... int c = do_op(4,5,fadd); 

Questo esempio funzionerà! (Non sto suggerendo che sia un buon C ++ ma …). Quello che è successo è che do_op è stato rappresentato intorno alle firme delle varie funzioni, e ogni istanza separata scriverà un codice di coercizione di tipo diverso. Quindi il codice istanzato per do_op con fadd assomiglia a qualcosa:

 convert a and b from int to float. call the function ptr op with float a and float b. convert the result back to int and return it. 

In confronto, il nostro caso in base ai valori richiede una corrispondenza esatta sugli argomenti della funzione.

I puntatori di funzione possono essere passati come parametri del modello e questo fa parte del C ++ standard . Tuttavia nel modello vengono dichiarati e utilizzati come funzioni anziché come puntatore a funzione. Alla istanziazione del modello si passa l’indirizzo della funzione anziché solo il nome.

Per esempio:

 int i; void add1(int& i) { i += 1; } template void do_op_fn_ptr_tpl(int& i) { op(i); } i = 0; do_op_fn_ptr_tpl<&add1>(i); 

Se si desidera passare un tipo di functor come argomento del modello:

 struct add2_t { void operator()(int& i) { i += 2; } }; template void do_op_fntr_tpl(int& i) { op o; o(i); } i = 0; do_op_fntr_tpl(i); 

Diverse risposte passano un’istanza di functor come argomento:

 template void do_op_fntr_arg(int& i, op o) { o(i); } i = 0; add2_t add2; // This has the advantage of looking identical whether // you pass a functor or a free function: do_op_fntr_arg(i, add1); do_op_fntr_arg(i, add2); 

Il più vicino ansible a questo aspetto uniforms con un argomento modello è definire do_op due volte: una volta con un parametro non di tipo e una volta con un parametro di tipo.

 // non-type (function pointer) template parameter template void do_op(int& i) { op(i); } // type (functor class) template parameter template void do_op(int& i) { op o; o(i); } i = 0; do_op<&add1>(i); // still need address-of operator in the function pointer case. do_op(i); 

Onestamente, mi aspettavo davvero che non si compilasse, ma ha funzionato per me con gcc-4.8 e Visual Studio 2013.

Nel tuo modello

 template  void doOperation() 

Il parametro T è un parametro di modello non di tipo. Ciò significa che il comportamento della funzione template cambia con il valore del parametro (che deve essere corretto al momento della compilazione, quali sono le costanti del puntatore di funzione).

Se vuoi qualcosa che funzioni con gli oggetti funzione e i parametri di funzione hai bisogno di un modello digitato. Tuttavia, quando si esegue questa operazione, è necessario fornire un’istanza dell’object (istanza dell’object funzione o un puntatore funzione) alla funzione in fase di esecuzione.

 template  void doOperation(T t) { int temp=0; t(temp); std::cout << "Result is " << temp << std::endl; } 

Ci sono alcune considerazioni sulle prestazioni minori. Questa nuova versione potrebbe essere meno efficiente con gli argomenti del puntatore di funzione poiché il particolare puntatore di funzione viene solo distriggersto e richiamato in fase di esecuzione mentre il modello del puntatore di funzione può essere ottimizzato (probabilmente la chiamata di funzione inline) in base al particolare puntatore di funzione utilizzato. Gli oggetti funzione possono spesso essere espansi in modo molto efficiente con il modello digitato, anche se il particolare operator() è completamente determinato dal tipo dell'object funzione.

Il motivo per cui il tuo esempio di funzionamento non funziona è che hai bisogno di un’istanza per richiamare l’ operator() .

Modifica: il passaggio dell’operatore come riferimento non funziona. Per semplicità, comprenderlo come un puntatore a funzione. Basta inviare il puntatore, non un riferimento. Penso che tu stia cercando di scrivere qualcosa come questo.

 struct Square { double operator()(double number) { return number * number; } }; template  double integrate(Function f, double a, double b, unsigned int intervals) { double delta = (b - a) / intervals, sum = 0.0; while(a < b) { sum += f(a) * delta; a += delta; } return sum; } 

. .

 std::cout << "interval : " << i << tab << tab << "intgeration = " << integrate(Square(), 0.0, 1.0, 10) << std::endl;