Matching delle Funzioni in C++
- Il matching è quel processo di selezione della funzione appropriata da un insieme di funzioni sovraccaricate in base agli argomenti forniti nella chiamata.
- Il processo di matching delle funzioni avviene in tre passi: determinare le funzioni candidate e fattibili, trovare la corrispondenza migliore, se ce n'è una, e gestire le conversioni dei tipi degli argomenti.
- Le conversioni sono classificate in cinque categorie: corrispondenza esatta, corrispondenza attraverso una conversione
const, corrispondenza attraverso una promozione, corrispondenza attraverso una conversione aritmetica e corrispondenza attraverso una conversione di tipo classe. - La corrispondenza delle funzioni può essere complicata quando ci sono più argomenti o quando i tipi degli argomenti richiedono conversioni.
Matching delle Funzioni
Il concetto di matching delle funzioni (o risoluzione delle funzioni) si riferisce al processo di selezione della funzione appropriata da un insieme di funzioni sovraccaricate in base agli argomenti forniti nella chiamata.
In molti casi, è facile capire quale funzione sovraccaricata corrisponde a una data chiamata. Tuttavia, non è così semplice quando le funzioni sovraccaricate hanno lo stesso numero di parametri e quando uno o più parametri hanno tipi che sono correlati da conversioni.
Come esempio, consideriamo il seguente insieme di funzioni e chiamata di funzione:
// Funzione senza parametri
void f();
// Funzione con un parametro int
void f(int);
// Funzione con due parametri int
void f(int, int);
// Funzione con due parametri double
// di cui il secondo ha un argomento predefinito
void f(double, double = 3.14);
// chiama void f(double, double)
f(5.6);
Il processo di matching delle funzioni avviene in tre passi. Studiamo ciascun passo in dettaglio.
Determinare le Funzioni Candidate e Fattibili
Il primo passo della corrispondenza delle funzioni identifica l'insieme di funzioni sovraccaricate considerate per la chiamata. Le funzioni in questo insieme sono le funzioni candidate. Una funzione candidata è una funzione con lo stesso nome della funzione chiamata e per cui una dichiarazione è visibile al punto della chiamata. In questo esempio, ci sono quattro funzioni candidate chiamate f.
Il secondo passo seleziona dall'insieme delle funzioni candidate quelle funzioni che possono essere chiamate con gli argomenti nella chiamata data. Le funzioni selezionate sono le funzioni fattibili. Per essere fattibile, una funzione deve avere lo stesso numero di parametri quanti sono gli argomenti nella chiamata, e il tipo di ogni argomento deve corrispondere, o essere convertibile, al tipo del suo parametro corrispondente.
Possiamo eliminare due delle nostre funzioni candidate basandoci sul numero di argomenti. La funzione che non ha parametri e quella che ha due parametri int non sono fattibili per questa chiamata. La nostra chiamata ha solo un argomento, e queste funzioni hanno rispettivamente zero e due parametri.
La funzione che prende un singolo int e la funzione che prende due double potrebbero essere fattibili. Entrambe queste funzioni possono essere chiamate con un singolo argomento. La funzione che prende due double ha un argomento predefinito, il che significa che può essere chiamata con un singolo argomento.
Bisogna ricordare, infatti, che quando una funzione ha argomenti predefiniti, una chiamata può sembrare avere meno argomenti di quanti ne abbia effettivamente.
Dopo aver usato il numero di argomenti per vagliare le funzioni candidate, guardiamo successivamente se i tipi degli argomenti corrispondono a quelli dei parametri. Come con qualsiasi chiamata, un argomento può corrispondere al suo parametro sia perché i tipi corrispondono esattamente sia perché esiste una conversione dal tipo dell'argomento al tipo del parametro. In questo esempio, entrambe le nostre funzioni rimanenti sono fattibili:
f(int)è fattibile perché esiste una conversione che può convertire l'argomento di tipodoubleal parametro di tipoint.f(double, double)è fattibile perché viene fornito un argomento predefinito per il secondo parametro della funzione e il suo primo parametro è di tipodouble, che corrisponde esattamente al tipo dell'argomento nella chiamata.
Se non ci sono funzioni fattibili, il compilatore si lamenterà che non c'è una funzione corrispondente.
Trovare la corrispondenza migliore, se ce n'è una
Il terzo passo della corrispondenza delle funzioni determina quale funzione fattibile fornisce la migliore corrispondenza per la chiamata. Questo processo guarda ogni argomento nella chiamata e seleziona la funzione fattibile (o le funzioni) per cui il parametro corrispondente corrisponde meglio all'argomento. Spiegheremo i dettagli di "migliore" nella prossima sezione, ma l'idea è che più vicini sono i tipi dell'argomento e del parametro l'uno all'altro, migliore è la corrispondenza.
Nel nostro caso, c'è solo un argomento (esplicito) nella chiamata. Quell'argomento ha tipo double. Per chiamare f(int), l'argomento dovrebbe essere convertito da double a int. L'altra funzione fattibile, f(double, double), è una corrispondenza esatta per questo argomento. Una corrispondenza esatta è migliore di una corrispondenza che richiede una conversione. Quindi, il compilatore risolverà la chiamata f(5.6) come una chiamata alla funzione che ha due parametri double. Il compilatore aggiungerà l'argomento predefinito per il secondo argomento mancante.
Corrispondenza delle Funzioni con Parametri Multipli
La corrispondenza delle funzioni è più complicata se ci sono due o più argomenti. Date le stesse funzioni dell'esempio f, analizziamo la seguente chiamata:
// chiama f(int, int) o f(double, double)?
f(42, 2.56);
L'insieme di funzioni fattibili è selezionato allo stesso modo di quando c'è solo un parametro. Il compilatore seleziona quelle funzioni che hanno il numero richiesto di parametri e per cui i tipi degli argomenti corrispondono ai tipi dei parametri. In questo caso, le funzioni fattibili sono f(int, int) e f(double, double). Il compilatore determina quindi, argomento per argomento, quale funzione è (o funzioni sono) la corrispondenza migliore. C'è una corrispondenza migliore complessiva se c'è una e una sola funzione per cui
- La corrispondenza per ogni argomento non è peggiore della corrispondenza richiesta da qualsiasi altra funzione fattibile;
- C'è almeno un argomento per cui la corrispondenza è migliore della corrispondenza fornita da qualsiasi altra funzione fattibile.
Se dopo aver guardato ogni argomento non c'è una singola funzione che è preferibile, allora la chiamata è in errore. Il compilatore si lamenterà che la chiamata è ambigua.
In questa chiamata, quando guardiamo solo il primo argomento, troviamo che la funzione f(int, int) è una corrispondenza esatta. Per corrispondere alla seconda funzione, l'argomento int 42 deve essere convertito a double. Una corrispondenza attraverso una conversione built-in è "meno buona" di una che è esatta. Considerando solo il primo argomento, f(int, int) è una corrispondenza migliore di f(double, double).
Quando guardiamo il secondo argomento, f(double, double) è una corrispondenza esatta all'argomento 2.56. Chiamare f(int, int) richiederebbe che 2.56 sia convertito da double a int. Quando consideriamo solo il secondo parametro, la funzione f(double, double) è una corrispondenza migliore.
Il compilatore rigetterà questa chiamata perché è ambigua: Ogni funzione fattibile è una corrispondenza migliore dell'altra su uno degli argomenti della chiamata. Si potrebbe essere tentati di forzare una corrispondenza facendo esplicitamente il cast di uno dei nostri argomenti. Tuttavia, in sistemi ben progettati, i cast degli argomenti non dovrebbero essere necessari.
I cast non dovrebbero essere necessari per chiamare una funzione sovraccaricata. Il bisogno di un cast suggerisce che gli insiemi di parametri sono progettati male.
Esercizio
-
Date le dichiarazioni per la funzione
fviste sopra, elenca le funzioni fattibili, se ce ne sono, per ognuna delle seguenti chiamate. Indica quale funzione è la corrispondenza migliore, o se la chiamata è illegale se non c'è corrispondenza o perché la chiamata è ambigua.f(2.56, 42); f(42); f(42, 0); f(2.56, 3.14);Risposta:
f(2.56, 42);Le funzioni fattibili sonof(int, int)ef(double, double). La chiamata è ambigua.f(42);La funzione fattibile èf(int). La chiamata corrisponde af(int).f(42, 0);La funzione fattibile èf(int, int). La chiamata corrisponde af(int, int).f(2.56, 3.14);La funzione fattibile èf(double, double). La chiamata corrisponde af(double, double).
Conversioni dei Tipi degli Argomenti
Per determinare la corrispondenza migliore, il compilatore classifica le conversioni che potrebbero essere usate per convertire ogni argomento al tipo del suo parametro corrispondente.
Le conversioni sono classificate come segue:
-
Una corrispondenza esatta:
Una corrispondenza esatta avviene quando:
- I tipi dell'argomento e del parametro sono identici.
- L'argomento è convertito da un tipo array o funzione al corrispondente tipo puntatore.
- Un
constdi livello superiore è aggiunto o scartato dall'argomento.
-
Corrispondenza attraverso una conversione
const; -
Corrispondenza attraverso una promozione;
-
Corrispondenza attraverso una conversione aritmetica;
-
Corrispondenza attraverso una conversione di tipo classe (che studieremo nelle prossime lezioni).
Corrispondenze che Richiedono Promozione o Conversione Aritmetica
Le promozioni e le conversioni tra i tipi built-in possono produrre risultati sorprendenti nel contesto della corrispondenza delle funzioni. Fortunatamente, i sistemi ben progettati includono raramente funzioni con parametri strettamente correlati come quelli negli esempi seguenti.
Per analizzare una chiamata, è importante ricordare che i piccoli tipi interi sempre promuovono a int o a un tipo intero più grande. Date due funzioni, una delle quali prende un int e l'altra uno short, la versione short sarà chiamata solo su valori di tipo short. Anche se i valori interi più piccoli potrebbero sembrare una corrispondenza più vicina, quei valori sono promossi a int, mentre chiamare la versione short richiederebbe una conversione:
void ff(int);
void ff(short);
// char promosso a int; chiama f(int)
ff('a');
Tutte le conversioni aritmetiche sono trattate come equivalenti l'una all'altra. La conversione da int a unsigned int, per esempio, non ha precedenza sulla conversione da int a double. Come esempio concreto, consideriamo
void manipola(long);
void manipola(float);
// ERRORE: chiamata ambigua
manipola(3.14);
Il letterale 3.14 è un double. Quel tipo può essere convertito sia a long che a float. Poiché ci sono due possibili conversioni aritmetiche, la chiamata è ambigua.
Corrispondenza delle Funzioni e Argomenti const
Quando chiamiamo una funzione sovraccaricata che differisce sul fatto che un parametro riferimento o puntatore si riferisca o punti a const, il compilatore usa la costanza dell'argomento per decidere quale funzione chiamare:
// funzione che prende un riferimento ad Account
Record cerca(Account &);
// funzione che prende un riferimento a const Account
Record cerca(const Account&);
const Account a;
Account b;
cerca(a); // chiama cerca(const Account&)
cerca(b); // chiama cerca(Account&)
Nella prima chiamata, passiamo l'oggetto const a. Non possiamo legare un riferimento normale a un oggetto const. In questo caso l'unica funzione fattibile è la versione che prende un riferimento a const. Inoltre, quella chiamata è una corrispondenza esatta all'argomento a.
Nella seconda chiamata, passiamo l'oggetto non const b. Per questa chiamata, entrambe le funzioni sono fattibili. Possiamo usare b per inizializzare un riferimento sia a const che a non const. Tuttavia, inizializzare un riferimento a const da un oggetto non const richiede una conversione. La versione che prende un parametro non const è una corrispondenza esatta per b. Quindi, la versione non const è preferita.
I parametri puntatore funzionano in modo simile. Se due funzioni differiscono solo per il fatto che un parametro puntatore punti a const o non const, il compilatore può distinguere quale funzione chiamare basandosi sulla costanza dell'argomento: Se l'argomento è un puntatore a const, la chiamata corrisponderà alla funzione che prende un const *; altrimenti, se l'argomento è un puntatore a non const, viene chiamata la funzione che prende un puntatore normale.
Esercizi
-
Date le seguenti dichiarazioni,
void manipola(int, int); double oggettoDouble;A quale delle 5 classificazioni viste sopra corrisponde ogni conversione nelle seguenti chiamate?
manipola('a', 'z'); manipola(55.4, oggettoDouble);Risposta:
manipola('a', 'z');Entrambi gli argomenti sono di tipochar, che viene promosso aint. Quindi, entrambe le conversioni sono classificabili come promozioni: tipo 3.manipola(55.4, oggettoDouble);Il primo argomento è di tipodouble, che deve essere convertito aint. Questa conversione è classificabile come conversione aritmetica: tipo 4. Il secondo argomento è di tipodouble, che corrisponde esattamente al tipo del parametroint. Quindi, questa conversione è classificabile come corrispondenza esatta: tipo 1.
-
Spiega l'effetto della seconda dichiarazione in ognuno dei seguenti insiemi di dichiarazioni. Indica quali, se ce ne sono, sono illegali.
int calcola(int&, int&); int calcola(const int&, const int&); int calcola(char*, char*); int calcola(const char*, const char*); int calcola(char*, char*); int calcola(char* const, char* const);Risposta:
- Nel primo insieme di dichiarazioni, entrambe le funzioni sono legali. La seconda funzione prende riferimenti a
const int, che possono essere inizializzati sia da oggetticonstche nonconst. La prima funzione prende riferimenti aint, che possono essere inizializzati solo da oggetti nonconst. - Nel secondo insieme di dichiarazioni, entrambe le funzioni sono legali. La seconda funzione prende puntatori a
const char, che possono essere inizializzati sia da puntatori aconstche nonconst. La prima funzione prende puntatori achar, che possono essere inizializzati solo da puntatori a nonconst. - Nel terzo insieme di dichiarazioni, entrambe le funzioni sono legali. La seconda funzione prende puntatori
char* const, che sono puntatori costanti achar. Questi puntatori possono essere inizializzati da puntatori a nonconst. La prima funzione prende puntatori achar, che possono essere inizializzati da puntatori a nonconst.
- Nel primo insieme di dichiarazioni, entrambe le funzioni sono legali. La seconda funzione prende riferimenti a