Esercizio - Template, specializzazione e deduzione degli argomenti (quarto appello 2021-22, teoria)
In questa pagina 4
Domanda aperta 2 del quarto appello 2021-22 (12 settembre 2022) del corso di Laboratorio di Programmazione, Ing. Informatica UniPD. Teoria: C++ - template e programmazione genericaun template permette di usare un tipo (o un intero noto in compilazione) come parametro di una classe o di una funzione; il compilatore genera il codice per ogni tipo per cui il template è specializzato (template instantiation, a tempo di compilazione): il codice è efficiente ma gli errori sono lunghi e oscuri, e il compilatore controlla sia il template sia il suo argomento; la notazione è template
Testo. Descrivere il concetto di template in C++ e la notazione utilizzata per definire una funzione template e per specializzarla per un determinato tipo. Riportare inoltre i principali vantaggi e svantaggi di questo meccanismo. Descrivere il concetto di deduzione degli argomenti del template operata in fase di compilazione.
Il concetto
Un template permette di usare un tipo come parametro (o anche un intero noto in compilazione) per una funzione o per una classe: si scrive il codice una volta sola e il compilatore genera la versione per ogni tipo per cui il template è usato (specializzazione o instantiation). La generazione avviene a tempo di compilazione. È il meccanismo del polimorfismo statico e della programmazione generica.
Notazione
Funzione template: si premette template <typename T> (equivalente a template <class T>: typename è più chiaro, class più corto) alla definizione:
template <typename T>
T myMax(T x, T y)
{
return (x > y) ? x : y;
}Si può indicare il tipo (myMax<int>(i, j)) o lasciarlo dedurre (myMax(i, j)).
Specializzazione esplicita per un tipo: si scrive template <> (lista vuota) e si indica il tipo dopo il nome della funzione, fornendo un'altra implementazione:
template <>
const char* myMax<const char*>(const char* x, const char* y)
{
return (strcmp(x, y) > 0) ? x : y; // la versione generica confronterebbe gli indirizzi
}#include <cstring>
#include <iostream>
#include <string>
using namespace std;
template <typename T>
T myMax(T x, T y) // T e' un tipo qualunque purché supporti l'operatore >
{
return (x > y) ? x : y;
}
// specializzazione ESPLICITA per const char*: il confronto generico (>) confronterebbe gli indirizzi
template <>
const char* myMax<const char*>(const char* x, const char* y)
{
return (strcmp(x, y) > 0) ? x : y;
}
struct Punto { int x, y; }; // NON ha operator>
int main()
{
int i = 3, j = 5;
cout << myMax<int>(i, j) << '\n'; // T indicato esplicitamente
cout << myMax(i, j) << '\n'; // T = int, DEDOTTO dagli argomenti
cout << myMax(2.5, -1.0) << '\n'; // T = double
cout << myMax(string("pesca"), string("mela")) << '\n'; // T = string
cout << myMax("pesca", "mela") << '\n'; // T = const char*: usa la specializzazione
// myMax(1, 2.5); // errore: T sarebbe int e double insieme
// myMax(Punto{1, 2}, Punto{3, 4}); // errore: Punto non ha operator>
return 0;
}5
5
2.5
pesca
pescaNel programma myMax("pesca", "mela") usa la specializzazione (T = const char*, perché i literal decadono a puntatore) e restituisce pesca; la chiamata myMax(1, 2.5) non compila per un conflitto di tipi.
Vantaggi e svantaggi
| vantaggi | svantaggi |
|---|---|
| codice generico scritto una volta | errori di compilazione lunghi e oscuri: il compilatore controlla sia il template sia l'argomento (invalid operands to binary expression ('Punto' and 'Punto'), con la riga dell'istanza myMax<Punto> solo in una note) |
| nessun costo a run-time: il codice è per il tipo esatto, inlinabile, ottimizzabile (a differenza del polimorfismo dinamico) | più codice generato (una copia per tipo), compilazione più lenta, eseguibili più grandi |
| i controlli di tipo restano in compilazione | poca separazione fra dichiarazione e definizione: le definizioni vanno negli header (nei .hpp inclusi in coda al .h) |
funziona anche con tipi built-in e con interi come parametri (array<T, N>) |
requisiti impliciti su T (qui operator>): non scritti nell'interfaccia, si scoprono con l'errore |
Deduzione degli argomenti
Il compilatore deduce gli argomenti template dagli argomenti della chiamata, a tempo di compilazione, senza che il programmatore li scriva. Con myMax(i, j), dove i e j sono int, ricava T = int: equivale a myMax<int>(i, j). Con parametri come array<T, N>&, ricava anche il valore intero N:
template<class T, int N>
void fill(array<T, N>& b, const T& val) { for (int i = 0; i < N; ++i) b[i] = val; }
fill(buf, 'x'); // T = char e N = 1024, dedotti da buf: equivale a fill<char, 1024>(buf, 'x')Regole utili: la deduzione usa i tipi degli argomenti; se lo stesso T appare con due tipi diversi (myMax(1, 2.5)) c'è conflitto e il template è scartato (deduced conflicting types for parameter 'T' ('int' vs. 'double')); i parametri che non compaiono fra gli argomenti non si possono dedurre e vanno indicati (vector<int> v;); gli argomenti di classe template (vector<double>) si indicano sempre fra <> (da C++17 a volte sono deducibili dal costruttore).
Versione ripasso
- Template = tipo (o intero) come parametro di funzione o classe; il compilatore genera il codice per ogni tipo usato (specializzazione/instantiation) a tempo di compilazione; polimorfismo statico.
- Notazione:
template <typename T>(template <class T>) davanti alla definizione;template <typename T> T myMax(T x, T y) { return (x > y) ? x : y; }. Specializzazione:template <> const char* myMax<const char*>(const char* x, const char* y) { return strcmp(x, y) > 0 ? x : y; }.
| vantaggi | svantaggi |
|---|---|
| codice generico, scritto una volta | errori lunghi e oscuri |
| nessun costo a run-time | più codice generato, compilazione più lenta |
| controlli di tipo in compilazione | definizioni negli header (.h + .hpp) |
anche tipi built-in e interi (array<T, N>) |
requisiti impliciti su T (>) |
- Deduzione: il compilatore ricava gli argomenti template dagli argomenti della chiamata:
myMax(i, j)T = int(myMax<int>(i, j));fill(buf, 'x')T = char,N = 1024. Tipi diversi per lo stessoT(myMax(1, 2.5)) = conflitto (deduced conflicting types for parameter 'T' ('int' vs. 'double')); parametri non presenti negli argomenti vanno indicati. - Prova:
myMax<int>(3, 5)emyMax(3, 5)5;myMax(2.5, -1.0)2.5; stringhepesca;const char*con la specializzazionepesca. - Errori tipici: definire un template in un
.cpp(errore del linker); argomenti di tipi diversi per lo stessoT; dimenticaretemplate <>nella specializzazione; credere che i template siano a run-time.