Scrivilo una volta, usalo con ogni tipo
Nella pagina precedente hai ordinato un vector<int> con std::sort. Ma std::sort ordina anche un vector<string>, un vector<double> o un array delle tue struct, senza che nessuno abbia scritto un sort separato per ognuno. Non è magia e non è overloading. È un template: un unico pezzo di codice che il compilatore riutilizza per qualunque tipo gli passi.
Senza template dovresti copiare e incollare la stessa logica per ogni tipo. Ecco la stessa funzione maximum scritta tre volte, che è esattamente la duplicazione che i template esistono per eliminare:
int maximum(int a, int b) { return a > b ? a : b; }
double maximum(double a, double b) { return a > b ? a : b; }
string maximum(string a, string b) { return a > b ? a : b; }
I corpi sono identici. Cambiano solo i tipi. Un template ti permette di dire "questo funziona per qualsiasi tipo T" e di scriverlo una volta sola.
Template di funzione
Trasformi una funzione in un template aggiungendo template <typename T> davanti e usando T dove normalmente andrebbe un tipo concreto.
Nota che non hai mai scritto maximum<int> o maximum<double>. Il compilatore guarda gli argomenti e capisce quale deve essere T: questa è la deduzione degli argomenti del template. Ogni tipo diverso con cui la chiami fa sì che il compilatore istanzi (generi) dietro le quinte una funzione concreta separata.
Puoi anche indicare il tipo in modo esplicito quando la deduzione non aiuta, usando le parentesi angolari:
Nella deduzione si nasconde un'insidia comune. Dato che T deve essere un unico tipo, mescolare tipi di argomento diversi la manda in crisi:
maximum(3, 7.5); // ERRORE: T è int o double? Il compilatore si rifiuta di indovinare.
Puoi risolvere essendo espliciti, maximum<double>(3, 7.5), oppure dando a ogni parametro il suo parametro di tipo, come faremo adesso.
Più parametri di tipo
Un template non è limitato a un solo tipo. Elencane quanti ne servono, separati da virgole. Così scrivi una funzione i cui parametri possono essere di tipi diversi:
Quando il tipo di ritorno dipende dai parametri, lascia che sia il compilatore a calcolarlo con auto (da C++14 in poi), che si abbina in modo naturale ai template:
Template di classe
I template non sono solo per le funzioni: anche intere classi possono essere template. È esattamente così che funzionano i contenitori standard: vector<int>, la map chiave-valore e pair<A, B> sono tutti template di classe. Scrivi la struttura dati una volta sola e questa memorizza qualunque tipo tu le passi come parametro.
Ecco un piccolo Box generico che contiene un valore di qualsiasi tipo:
La differenza principale rispetto ai template di funzione: con un template di classe di solito devi indicare il tipo tra parentesi angolari, Box<int>, perché negli standard più vecchi non ci sono argomenti del costruttore da cui dedurlo. (C++17 ha aggiunto la deduzione degli argomenti dei template di classe, quindi funziona anche Box b(42);, ma essere espliciti è sempre sicuro e si legge meglio.)
Gli errori saranno enormi: ecco perché
Questa è la parte che mette in difficoltà tutti, quindi vale la pena dirla chiaramente. Un template viene controllato del tutto solo quando è istanziato con un tipo reale. Puoi scrivere un template che usa < e compilarlo senza problemi: l'errore compare solo nel momento in cui lo istanzi con un tipo che non ha <.
template <typename T>
T maximum(T a, T b) {
return a > b ? a : b; // richiede che T supporti >
}
struct Point { int x, y; };
// maximum(Point{1,2}, Point{3,4});
// ERRORE: nessun operator > per Point. Il messaggio nomina Point E
// cita tutta questa funzione, spesso su molte righe.
Poiché il compilatore sostituisce il tipo completo nel template e segnala i fallimenti dall'interno del codice generato, un singolo errore può produrre un muro di output che cita i dettagli interni della libreria. Due consigli per sopravvivere:
- Leggi il primo errore, non l'ultimo. Quelli successivi di solito sono conseguenze del primo.
- Cerca nel messaggio il nome del tuo tipo (qui,
Point). Ti dice quale istanziazione è andata storta.
La vera soluzione è assicurarsi che il tuo tipo supporti tutto ciò che serve al template: per maximum, significa fare l'overloading di operator> per Point, un argomento per una pagina successiva. I concept del C++20 moderno possono anticipare questi errori e renderli leggibili, ma il modello di sostituzione sottostante è lo stesso.
Prossimo: classi
Hai appena costruito un template di classe Box, una classe con dati privati, un costruttore e funzioni membro, concentrandoti però sui template. La prossima pagina rallenta e spiega le classi come si deve: come riunire i dati con le funzioni che ci lavorano, cosa controllano davvero public e private e come le funzioni membro accedono allo stato dell'oggetto. Template e classi si combinano di continuo nel C++ reale, quindi una buona padronanza delle classi rende il codice generico molto più facile da scrivere.
Domande frequenti
Cos'è un template in C++?
Un template è un modello che ti permette di scrivere una funzione o una classe una volta sola e lasciare che il compilatore ne generi una versione per ogni tipo con cui la usi. Scrivi template <typename T> e poi usi T al posto del tipo reale. Il compilatore produce una versione concreta: questo si chiama istanziazione.
Qual è la differenza tra typename e class in un template C++?
typename e class in un template C++?In un elenco di parametri di template, template <typename T> e template <class T> significano esattamente la stessa cosa. Oggi in genere si preferisce typename perché è più onesto: T può essere un tipo qualsiasi, non solo una classe. La scelta della parola chiave non ha alcun effetto sul codice generato.
Perché i messaggi di errore dei template C++ sono così lunghi?
I template vengono controllati quando sono istanziati con un tipo reale, non quando vengono scritti. Se un tipo non supporta un'operazione che hai usato (come < per ordinare), l'errore compare in profondità nel codice della libreria con il tipo istanziato scritto per intero, producendo pagine di output. Leggi il primo errore e cerca al suo interno il nome del tuo tipo.