Template di funzione e deduzione dei tipi
Impara a scrivere funzioni che funzionano con qualsiasi tipo. Comprendi la deduzione degli argomenti template, i parametri non di tipo e perché i template vivono negli header.
Perché i template di funzione?
Senza i template, scrivere una funzione che funzioni per più tipi significa duplicare la logica. Dovresti scrivere int max(int, int), double max(double, double), std::string max(std::string, std::string) — logica identica ripetuta per ogni tipo. I template di funzione ti permettono di scrivere l'algoritmo una volta sola e di lasciare che sia il compilatore a generare automaticamente le versioni specifiche per ciascun tipo.
Un template di funzione non è una funzione: è un modello a partire dal quale il compilatore istanzia funzioni concrete quando lo usi con tipi specifici. Tutto questo avviene interamente a tempo di compilazione e produce codice identico a quello che scriveresti a mano — zero overhead.
Sintassi dei template
Un template di funzione si introduce con la parola chiave template seguita da una lista di parametri tra parentesi angolari. Per i parametri di tipo puoi usare indifferentemente typename o class — significano esattamente la stessa cosa. La convenzione moderna preferisce typename.
#include <iostream>
#include <string>
// 'typename' and 'class' are interchangeable here
template <typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
// Equivalent — 'class' means the same thing
template <class T>
T minimum(T a, T b) {
return (a < b) ? a : b;
}
int main() {
std::cout << maximum(3, 7) << '\n'; // deduces T = int
std::cout << maximum(3.14, 2.71) << '\n'; // deduces T = double
std::cout << maximum<std::string>("hello", "world") << '\n';
// explicit instantiation needed — "hello" is const char*, not std::string
}Regole di deduzione degli argomenti template
Il compilatore deduce gli argomenti template confrontando i tipi degli argomenti della funzione con i pattern dei parametri. Le regole fondamentali sono:
1. Si preferisce la corrispondenza esatta — maximum(3, 7) deduce T = int perché entrambi gli argomenti sono int.
2. Nessuna conversione implicita durante la deduzione — maximum(3, 7.0) è un errore: il compilatore ricava T = int dal primo argomento e T = double dal secondo, e si rifiuta di sceglierne uno.
3. Decadimento da array a puntatore — quando T compare come parametro per valore, gli array decadono a puntatori. Usa T(&)[N] per catturare il tipo dell'array e la sua dimensione.
4. Il const/volatile di primo livello viene rimosso dai parametri per valore, ma è preservato per i parametri riferimento o puntatore.
5. Collasso dei riferimenti — se T viene dedotto come tipo riferimento (nelle forwarding reference) si applicano le regole di collasso: T& & diventa T&, T& && diventa T&, T&& & diventa T&, T&& && diventa T&&.
Quando la deduzione fallisce o è ambigua, devi fornire gli argomenti template esplicitamente: maximum.
Parametri template non di tipo
I parametri template non si limitano ai tipi. I parametri template non di tipo (NTTP) accettano valori noti a tempo di compilazione come interi, puntatori o, da C++20, valori in virgola mobile e tipi classe. Sono potentissimi per incorporare costanti di compilazione nel sistema dei tipi.
#include <iostream>
#include <array>
#include <cstddef>
// Non-type parameter: N is a compile-time size_t value
template <typename T, std::size_t N>
T sum(const std::array<T, N>& arr) {
T total{};
for (const auto& elem : arr) {
total += elem;
}
return total;
}
// C++20: auto non-type parameter accepts any structural type
template <auto Value>
constexpr auto doubled = Value * 2;
int main() {
std::array<int, 4> nums = {1, 2, 3, 4};
std::cout << sum(nums) << '\n'; // 10, deduces T=int, N=4
std::cout << doubled<21> << '\n'; // 42
std::cout << doubled<3.14> << '\n'; // 6.28 (C++20)
}Ogni combinazione univoca di argomenti template porta il compilatore a generare un'istanziazione separata — una copia distinta della funzione nel tuo binario. Chiamare maximum, maximum e maximum produce tre funzioni. Di solito non è un problema, ma nei progetti di grandi dimensioni i template possono contribuire al code bloat (binari più grandi, tempi di compilazione più lunghi). Tra le strategie di mitigazione ci sono: usare dichiarazioni extern template per controllare dove avvengono le istanziazioni, estrarre dai template il codice indipendente dal tipo e ricorrere alla type erasure quando il polimorfismo a runtime è accettabile.
Poiché il compilatore ha bisogno di vedere la definizione completa del template per istanziarlo, i template non possono essere divisi tra file .h e .cpp come le funzioni normali. Se metti la definizione di un template in un file .cpp e provi a usarlo da un'altra unità di traduzione, otterrai un errore del linker (riferimento non definito). La soluzione standard è mettere l'intera definizione del template nel file header. Se i tempi di compilazione diventano un problema, puoi ricorrere all'istanziazione esplicita: metti la definizione in un file .cpp e aggiungi template int maximum per istanziare esplicitamente le specializzazioni che ti servono.
Istanziazione esplicita
Puoi controllare esplicitamente dove e quando i template vengono istanziati. Nei progetti grandi questo riduce i tempi di compilazione evitando istanziazioni ridondanti tra unità di traduzione.
// ---- maximum.h ----
template <typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
// Suppress implicit instantiation in every TU that includes this header
extern template int maximum<int>(int, int);
extern template double maximum<double>(double, double);
// ---- maximum.cpp ----
#include "maximum.h"
// Explicit instantiation: generate code here, once
template int maximum<int>(int, int);
template double maximum<double>(double, double);- I template di funzione sono modelli — il compilatore genera funzioni concrete per ogni insieme di argomenti template
typenameeclasssono intercambiabili nelle liste di parametri template- La deduzione degli argomenti template non esegue conversioni implicite — le deduzioni ambigue sono errori
- I parametri template non di tipo incorporano valori di compilazione nel sistema dei tipi
- I template vanno definiti negli header (o serve l'istanziazione esplicita) perché il compilatore ha bisogno della definizione completa per istanziarli
Quiz — Metti alla prova le tue conoscenze
(15 XP)1. Cosa succede quando chiami `maximum(3, 7.0)` con il template `template<typename T> T maximum(T a, T b)`?
2. Perché le definizioni dei template vanno tipicamente collocate nei file header?
3. Qual è lo scopo di `extern template int maximum<int>(int, int);`?