Cito il seguente periodo dell’articolo:
“Comunque, l’idea fondamentale è che è necessario definire alcune funzioni anziché ereditare da una classe base. Qualsiasi classe che “si comporta” come un InputIterator è allora considerata un InputIterator.”
Mi trovo ovviamente d’accordo con la seconda affermazione. Per quanto riguarda la prima affermazione varrebbe spendere qualche parolina in più.
Al lettore più ingenuo, soprattutto, vorrei dire che lo standard, in realtà, definisce intenzionalmente la classe template std::iterator come classe base da cui si dovrebbe sempre ereditare, parametrizzandola in maniera opportuna, quando si implementa un iteratore.
std::iterator definisce solo tipi membro e viene “configurata” sulla base dei tipi di argomenti ad essa passati. I tipi così definiti servono ad un’altra classe template dello standard, std::iterator_traits (che prende come parametro il tipo iteratore *standard*), per determinare alcune proprietà degli iteratori, le quali possono servire alle implementazioni degli algoritmi generici dello standard. La particolarità di std::iterator_traits, infatti, è che è specializzata per gli iteratori che sono puntatori a elementi di tipo T, T* e const T* nello specifico: ciò consente di definire una sola implementazione di un dato algoritmo (vedi sotto per un esempio). E allo stesso modo l’utente può scrivere altri algoritmi generici *senza* bisogno di doverli specializzare per iteratori di tipo puntatore, sfruttando std::iterator_traits.
Un classico esempio è count(). Con interator_traits si può scrivere una sola volta:
template
typename iterator_traits
count(In first, In last, const T& val) { /*...*/ }
Si può scrivere, cioè, un’implementazione valida anche quando l’iteratore è di tipo T* o const T*,perchè std::iterator_traits è specializzato per iteratori di tipo T* e const T* (quando In deriva da std::iterator appunto).
Al contrario, se non si usasse iterator_traits (o non si potesse usare perchè il proprio iteratore non è derivato opportunamente, come dicevo prima, da std::iterator), allora si sarebbe costretti a scrivere, per essere “veramente generici”, più implementazioni di count(), di cui una una specializzata per T*, per esempio:
template
typename In::difference_type count(In first, In last, const T& v);
template
Se si devono scrivere molti algoritmi generici, si capisce bene il vantaggio della coppia std::iterator e std::iterator_traits.
Non vorrei aver dato un taglio “avanzato” alla discussione, ma direi che il prossimo spunto può essere quello di imparare a conoscere/riconoscere le varie categorie di iteratori per saper approfittare adeguatamente di std::interator e std::iterator_traits.
]]>In realtà, quando parli di idioma spesso usato per scorrere una sequenza mediante un InputIterator, si dovrebbe preferire operator!=() come condizione di terminazione del ciclo, anzichè operator<(). La nota è sostanziale, proprio perchè gli InputIterator (oggetto del discorso) non devono definire necessariamente un ordinamento.
]]>