Nelle lezioni precedenti abbiamo visto che il costrutto template in C++ consente di generalizzare porzioni di codice delegando l'associazione dei tipi ad un processo computazionale che prende il nome di istanziazione.
Il compilatore ha il compito di adattare, se possibile, l'invocazione di una funzione template o la dichiarazione di un'istanza appartenente ad una classe template ad ogni tipologia di dato.
Pur essendo questo un meccanismo estremamente potente e comodo per il programmatore, tuttavia, a volte sorge l'esigenza di differenziarlo per tipi che presentano caratteristiche peculiari rispetto al caso generale.
La specializzazione di template è una tecnica che consente di soddisfare tale requisito. Essa consiste nella ridefinizione di un blocco di codice generico in cui uno o più argomenti template sono sostituiti con valori espliciti.
In presenza di una o più specializzazioni, il codice template di base è detto template primario. Il compilatore seleziona l'istanza corretta applicando le consuete regole di inferenza dei tipi, ricercando il riscontro più stringente tra le opzioni disponibili. Il template primario viene usato quando nessuna delle specializzazioni è applicabile.
Come esempio, nel listato seguente vengono fornite due specializzazioni per il tipo int
e per il tipo bool
di una funzione generica di nome test()
.
#include <iostream>
// template primario
template <typename T>
void test()
{
std::cout << "Primary template used.\n";
}
// specializzazione per il tipo int
template <>
void test<int>()
{
std::cout << "Int specialization used.\n";
}
// specializzazione per il tipo bool
template <>
void test<bool>()
{
std::cout << "Bool specialization used.\n";
}
int main()
{
test<float>();
test<int>();
test<bool>();
test<unsigned int>();
test<const int>();
return 0;
}
Si osservi l'assenza di parametri nel preambolo template <>
che contraddistingue le due specializzazioni.
L'output prodotto dalle chiamate alle varie istanze delle funzione generica è il seguente:
Primary template used.
Int specialization used.
Bool specialization used.
Primary template used.
Primary template used.
Si evince chiaramente l'uso delle specializzazioni in corrispondenza dell'uso dei parametri template int
e bool
. Si osservi inoltre l'effetto dell'uso del modificatori (unsigned
) e dei qualificatori (in questo caso const
) che alterando il tipo int
, influiscono sul processo di istanziazione inducendo il compilatore a selezionare il template primario piuttosto che la specializzazione fornita.
Specializzazioni e applicazioni nella STL
La specializzazione di template ha un ruolo fondamentale nella definizione di moltissime API della Standard Template Library di C++.
Nella lezione precedente, analizzando il caso di una classe template idonea ad essere istanziata soltanto con tipi numerici, si è fatto cenno alla possibilità di restringere il processo di istanziazione soltanto a tipi con determinate caratteristiche.
Uno dei meccanismi che rendono possibile il rispetto di vincoli di questo tipo è proprio la specializzazione. A questo proposito si consideri il listato seguente in cui si sfrutta l'uso di specializzazioni per derivare informazioni sul tipo in uso a tempo di compilazione:
#include <iostream>
#include <string>
template <typename T>
class NumberTest
{
public:
static const bool value = false;
};
template <>
class NumberTest<int>
{
public:
static const bool value = true;
};
int main()
{
std::cout << std::boolalpha << NumberTest<std::string>::value << "\n";
std::cout << std::boolalpha << NumberTest<int>::value << "\n";
return 0;
}
La classe template NumberTest definisce un unico membro statico e costante di tipo booleano cui nella versione di base (o primaria) viene assegnato il valore false
. Della stessa classe viene fornita una specializzazione per il tipo int
in cui il valore di tale membro è true
.
L'implementazione della classe NumberTest è una versione semplificata di quella della classe template std::arithmetic
, usata nella lezione precedente per restringere il processo di istanziazione.
Per emulare il comportamento di std::arithmetic
è sufficiente aggiungere opportune specializzazioni per tutti i tipi numerici del linguaggio come char, long
, float
, double
eccetra.
La specializzazione è quindi una tecnica utile non solo per adattare l'esecuzione di codice generico a casi particolari, ma anche per la definizione di controlli statici basati esclusivamente sulla capacità del compilatore di associazione dei tipi.
Differenze tra classi e funzioni template
Le modalità di specializzazione di template in C++ mettono in evidenza alcune differenze cruciali tra le classi e le funzioni generiche. La prima di queste consiste nel fatto che una classe che presenta più argomenti template può essere specializzata del tutto o solo parzialmente. Ciò equivale a dire che non tutti gli argomenti template devono essere sostituiti da un valore esplicito.
Lo stesso non vale per le funzioni, cui non si possono applicare specializzazioni parziali. Un modo per arginare tale limitazione consiste nell'incapsulare funzioni template all'interno di classi in qualità di metodi statici.
Inoltre, nel caso di funzioni template la specializzazione ha una certa sovrapposizione semantica con il concetto di overloading.
Tuttavia, nonostante le affinità sul piano teorico, specializzazioni e overloading sono costrutti molto differenti.
Innanzi tutto, una funzione template può essere sovraccaricata mediante la definizione di un'altra funzione template o di una funzione normale. In questi casi è opportuno sapere che il compilatore predilige sempre l'uso di funzioni ordinarie rispetto quelle generiche, laddove esse siano applicabili.
Inoltre, durante il processo di istanziazione, il compilatore procede analizzando prima tutti gli overload disponibili, e solo in seguito, in assenza di un riscontro, procede all'analisi delle specializzazioni di un template. Tale circostanza ha ripercussioni subdole sul fatto che l'ordine di definizione di overloading e specializzazioni di funzioni template può influire sulla selezione del candidato scelto per l'istanziazione.
Per questi motivi, in generale, non è prudente definire specializzazioni e overloading della medesima funzione template.