Ho intenzione di iniziare questo dal punto di vista del C ++, quindi passare a C.
In linguaggi tipizzati staticamente come C, C ++, Java, ecc., una funzione "generica" consente di specificare le operazioni della funzione una volta , utilizzando segnaposto per qualsiasi tipo che può variare tra chiamate diverse (il che significa che funzioni come qsort
e bsearch
sono più decisamente non funzioni generiche). Idealmente, ti piacerebbe anche che il compilatore rilevasse automaticamente tutte le chiamate a questa funzione generica e generasse il codice reale, se necessario.
C ++ semplifica 1 offrendo modelli :
template <typename T>
T summation( T *values, size_t numValues )
{
T result = 0;
for ( size_t i = 0; i < numValues; i++ )
result += values[i];
return result;
}
T
è un segnaposto per qualsiasi tipo 2 , quindi puoi chiamarlo come
int ivals[] = {1,2,3,4,5,6,7,8,9};
double dvals[] = {1,2,3,4,5,6,7,8,9};
int sumi = summation( ivals, 10 );
double sumd = summation( dvals, 10 );
Quando il codice è compilato, il compilatore vede le due chiamate su summation
e deduce i tipi degli argomenti. Per ogni tipo diverso, genera una nuova istanza della funzione, dandole un nome univoco:
int summation_i( int *values, size_t numValues ) // actual compilers will generate
{ // more complex "mangled" names
int result = 0; // than this
...
}
double summation_d( double *values, size_t numValues )
{
double result = 0;
...
}
Quindi genera codice tale che il risultato di summation_i
è assegnato a sumi
e summation_d
è assegnato a sumd
.
C non offre nulla di simile alla funzione modello. Tradizionalmente, abbiamo attaccato la programmazione generica in due modi: utilizzando macro o utilizzando void *
ovunque e delegando operazioni di tipo aware ad altre funzioni.
Ecco un cattivo esempio di una soluzione basata su macro:
#include <stdio.h>
#define SUMMATION_DEF(t) \
t summation_##t( t *values, size_t numValues ) \
{ \
t result = 0; \
for ( size_t i = 0; i < numValues; i++ ) \
result += values[i]; \
return result; \
}
#define SUMMATION(t,x,s) summation_##t(x, s)
SUMMATION_DEF(int)
SUMMATION_DEF(double)
int main( void )
{
int ivals[] = {1, 2, 3, 4, 5};
double dvals[] = {1, 2, 3, 4, 5};
int sumi = SUMMATION(int, ivals, 5);
double sumd = SUMMATION(double, dvals, 5);
printf( "sumi = %d\n", sumi );
printf( "sumd = %f\n", sumd );
return 0;
}
Il SUMMATION_DEF
è approssimativamente simile a un modello in quanto specifica le operazioni della funzione, utilizzando il parametro macro t
come segnaposto di tipo. Usiamo anche t
come parte del nome della funzione - il ##
è l'operatore che incolla i token, e il preprocessore espanderà t
e aggiungerà quel valore al nome della funzione 3 .
Il punto in cui differisce dal C ++ è il fatto che una macro è solo una stupida sostituzione del testo. Non attiva alcuna operazione speciale sulla parte del compilatore. Le istanze della funzione effettiva non vengono generate automaticamente in base a qualsiasi richiamo della macro SUMMATION
: dobbiamo generare in modo esplicito le funzioni che vogliamo (quindi SUMMATION_DEF(int)
e SUMMATION_DEF(double)
prima di main
). Significa anche che quando chiamiamo summation_xxx
attraverso la macro SUMMATION
, dobbiamo passare il tipo come parte dell'elenco di argomenti macro, in modo tale che venga chiamata la funzione giusta. Che dolore.
Lo standard C 2011 ha aggiunto la parola chiave _Generic
, che può semplificare la vita a tale riguardo:
#include <stdio.h>
#define SUMMATION_DEF(t) \
t summation_##t( t *values, size_t numValues ) \
{ \
t result = 0; \
for ( size_t i = 0; i < numValues; i++ ) \
result += values[i]; \
return result; \
}
#define SUMMATION(x,s) _Generic((x), \
int * : summation_int, \
double * : summation_double \
)(x, s)
SUMMATION_DEF(int)
SUMMATION_DEF(double)
int main( void )
{
int ivals[] = {1, 2, 3, 4, 5};
double dvals[] = {1, 2, 3, 4, 5};
int sumi = SUMMATION(ivals, 5);
double sumd = SUMMATION(dvals, 5);
printf( "sumi = %d\n", sumi );
printf( "sumd = %f\n", sumd );
return 0;
}
La parola chiave _Generic
ti consente di valutare espressioni basate su tipi ; quindi, se il tipo del primo argomento a SUMMATION
è int *
, chiamiamo summation_int
; è double *
, chiamiamo summation_double
. In questo modo non dobbiamo specificare il nome del tipo negli argomenti della macro.
L'altro approccio, come hai visto, è utilizzare void *
e delegare le operazioni di tipo aware ad altre funzioni. Come ho detto sopra, non si tratta di una programmazione veramente "generica", poiché è necessario implementare manualmente ciascuna funzione di confronto per ciascun tipo. Non puoi semplicemente codificarlo una volta e averlo fatto. E usando void *
, in pratica getti la sicurezza del tipo fuori dalla finestra e nel traffico in arrivo.
E prima che qualcuno si lamenti - no, nessuna di queste funzioni di sommatoria verifica o gestisce l'overflow aritmetico. Questo è un argomento per un altro giorno.
- Per le definizioni sufficientemente sciolte di "facile". Il linguaggio di metaprogrammazione utilizzato per supportare i modelli è completo di Turing, quindi puoi fare * incredibilmente * e impossibile capire le cose con esso.
- Per le definizioni sufficientemente allentate di "qualsiasi tipo". Nota che qualunque sia il tipo che usi deve supportare l'operatore
+=
, altrimenti il compilatore ti urlerà contro.
- Questo codice sarà interrotto per tipi come
unsigned int
o long double
poiché hanno spazi bianchi nel nome. Non conosco immediatamente la soluzione a questo problema e ho dedicato abbastanza tempo a questa risposta.