Newsgroup: it.comp.lang.c++
Titolo: Abstract containers

Ciao a tutti,
vorrei una funzione, diciamo "condisci", che potesse accettare come
parametro qualsiasi tipo di abstract container contenente puntatori a
Pizza.
Mi spiego meglio: vorrei poterla chiamare sia così
vector <Pizza *> tavolo1;
//[...]
condisci(tavolo1); //opera su ogni Pizza contenuta in tavolo1
che così
map <std::string, Pizza *> tavolo2;
//[...]
condisci(tavolo2); //opera su ogni Pizza contenuta in tavolo2
E' possibile? Se c'è una classe da cui derivano sia map che vector
credo di sì... Forse c'è un modo migliore per farlo?
Grazie, giulio
Per favore togliete "!RemoveMe!" dall'indirizzo per rispondere via email
Please remove "!RemoveMe!" from the address to email me

La tecnica standard per fare queste cose non e' passare il contenitore,
ma i relativi iteratori begin() e end(); cosi' la tua "condisci" deve
solo sapere che con *i ottiene un puntatore a Pizza, con ++i passa
all'elemento successivo e col confronto con end() controlla se e'
arrivato alla fine. Attento alle map pero'...
Ciao,
Nicola

arrivato alla fine. Attento alle map pero'...
Eh, appunto, il problema si sposta sugli iteratori: nella
dichiarazione di condisci() posso mettere un iteratore generale che
"comprenda" sia vector<Pizza *>::iterator che la map<blabla, Pizza
*>::iterator ?
Grazie, ciao.


 credo di sě... Forse c'č un modo migliore per farlo?
No, non vi e` alcuna classe-base comune per std::map e
std::vector, e tanto meno vi e` polimorfismo a run-time fra
di essi in qualsivoglia maniera (nessuna funzione virtuale,
ecc, ecc).
Se guardi le funzioni dichiarate in <algorithm>, che affrontano
problemi analoghi (anche piu` ardui, certo, perche` non sanno
assolutamente nulla di alcun tipo dichiarato dall'utente...!),
coglierai un primo schema di approccio alla soluzione:
template <class It>
void condisci(It begin, It end)
{
    while(begin!=end) {
        manipola(*begin);
        ++begin;
    }
}
da chiamarsi, non esattamente come tu vorresti, bensi`:
    condisci(tavolo1.begin(), tavolo1.end());
    condisci(tavolo2.begin(), tavolo2.end());
Questo schema, comune a tutte le funzioni di <algorithm>,
e` molto piu` flessibile che non quello di "passare il
contenitore stesso come argomento", in quanto esso
permette, sia di operare sull'_intero_ contenitore, con
il comune idioma di passare begin ed end, sia su di
una PARTE del contenitore passando altri limiti, e anche
questo tante volte puo` fare comodo.
Se il tuo compilatore non ha problemi sugli overload di
template, puoi comunque anche aggiungere una
funzionicchina di comodo che fa appunto un overload:
template <class Cont>
inline void condisci(Cont& cont)
{
    condisci(cont.begin(), cont.end());
}
Con questo non abbiamo _ancora_ finito, comunque,
perche` resta da definire la "manipola".
Se It e` std::vector<Pizza*>::iterator, "manipola" verra`
chiamata con un Pizza*; se It e` invece
std::map<std::string,Pizza*>::iterator, "manipola"
verra` allora chiamata con una
    std::pair<std::string,Pizza*>.
Questo puoi risolverlo con semplici overload, se non
ti interesse continuare a propagare la genericita`:
void manipola(Pizza* p)
{
    // quel che devi davvero fare su p
}
inline void manipola(std::pair<std::string, Pizza*> p)
{
    manipola(p.second); // tipica delegazione
}
Questo sarebbe probabilmente "miope", a mio avviso.
Meglio, normalmente, mantenere la genericita` sino
a quando possibile.  Ad esempio, e _come minimo_,
visto che della _chiave_ relativa al Pizza* in una map
o multimap non potrebbe importarti di meno, la
versione "wrapper di delegazione" di manipola e`
sicuramente meglio posta come template:
template<class Chiave>
inline void manipola(std::pair<Chiave, Pizza*> p)
{
    manipola(p.second); // tipica delegazione
}
Ma come dicevo, questo e` ancora un discorso di
minima.  Con un uso appropriato della genericita`,
non sara` poi troppo arduo gestire Calzone, Gnocco
o anche Crescentina in modi analoghi a quelli in
cui gestisci Pizza, _anche senza nessuna base
comune_ e nessun polimorfismo _run-time_...
basta evitare di "cablare" quello che potresti lasciare
aperto, e definire delle convenzioni sui _nomi_ (e
la sintassi formale) di certi operatori/metodi/funzioni,
cosi` che i template possano operare la loro magia
di "polimorfismo compile-time" (per cosi` dire:-).
Alex

Per il caso della mappa si potrebbe, se non esiste gia', realizzare un
adattatore "spaiante", che deleghi tutto all'iteratore originale, ma che
ritorni i->second alla dereferenziazione. Qualcosa del tipo (esempio
incompleto, non compilato ne eseguito):
template <typename MapIterator> class unpairing_adapter {
  public:
    unpairing_adapter(const MapIterator & mi) : mapIter(mi) {}
    MapIterator::result_type::second_type operator * () 
	{ return mapIter->second; }
    unpairing_adapter operator ++ () { ++mapIter; return *this; }
    unpairing_adapter operator ++ (int) 
	{ unpairing_adapter ua(mapIter); ++mapIter; return ua; }
  private:
    MapIterator mapIter;
}
template <typename MapIterator> 
unpairing_adapter<MapIterator> unpair(const MapIterator & mi) {
  return unpairing_adapter<MapIterator>(mi);
}
In modo da chiamare condisci come:
map<string,Pizza *> lePizze;
condisci(unpair(lePizze.begin()),unpair(lePizze.end()));
O qualcosa di simile.
Ciao,
Nicola



Scusate il doppio post. Il cancel di Netscape e' meno sveglio di quanto
dovrebbe...
Ciao,
Nicola

 ritorni i->second alla dereferenziazione. Qualcosa del tipo (esempio
Giusto, e ne varra` la pena se si deve spesso "percorrere" una
mappa ignorandone le chiavi, mentre il mio approccio basato
sugli overload puo` essere preferibile in casi + semplici.
Alex