Newsgroup: it.comp.lang.c++
Titolo: overload dell operatore []

Salve,
Per un'applicazione a cui sto lavorando, ho bisogno di un vettore di
oggetti in memoria condivisa, ho provato cosi' a realizzare una template
class adatta allo scopo.
Ho incontrato pero' delle difficolta' nell'overload dell'operatore [].
Sviluppo in ambiente Unix, ma il problema e' generale e, nel sorgente
riportato, ho eliminato la parte legata al sistema operativo.
Ho realizzato una classe SharedVecRef<T> che non e' altro che un reference
a un elemento del vettore.  L'operatore [] della classe SharedVec<T>
ritorna uno SharedVecRef<T>, per quest'ultimo ho definito gli operatori
T() e T&() e l'operatore =(const T&) cosi' posso fare cose del genere:
T x;
x = vec[i];
vec[i] = x;
Il mio problema (non grave, per fortuna) e' che non riesco ad accedere
direttamente ai membri di un elemento del vettore ma devo prima appoggiarlo 
su un temporaneo, cioe' non riesco a fare
vec[i].f();
ma devo fare
T &x = vec[i]; x.f();
Qualcuno ha qualche idea per ovviare a questo inconveniente ?
Grazie in anticipo
Fabio
--------------------------------------------------------------------------
#include <new>
template <class T> class SharedVec ;
template <class T>
class SharedVecRef
{
      T *elem;
   public:
      SharedVecRef ( T *mem, unsigned int i) 
      {
         elem = (T *) (mem + i);
      }
      SharedVecRef &operator= (const T &el) 
      { 
        *elem = el; return *this; 
      }
      operator T () { return *elem; }
      operator T&() { return *elem; } 
};
template <class T>
class SharedVec
{
   int   id;
   T    *memory;
   int   num_elements;
   typedef SharedVecRef<T> Ref;
   public:
      SharedVec (int key, int num_el) : memory(0), num_elements(0)
      {
  	 bool creata = false;
 
	 memory = ... // accedo alla memoria condivisa,
		      // creandola se non esiste
	 num_elements = ... // se la memoria gia' esisteva, 
			    //potrebbe essere diverso da num_el
         if ( creata)
         { // so ho creato io la memoria, la inizializzo. 
           int i;
           for (i=0; i<num_elements;i++)
              new ((void *) (memory + i)) T;
         } 
      }
      ~SharedVec()
      {
   	 // ... 
      }
      int size() const { return num_elements; }
      Ref operator[] (unsigned int idx)
      {
         if ( (idx > num_elements ))
            throw ("Subscript out of range");
         return Ref (memory, idx);
      }
};

Qualcuno ha qualche idea per ovviare a questo inconveniente ?
Aggiungi al tuo template SharedVecRef<T> un modo ESPLICITO di
ottenere il T&, oltre a quello implicito che gia` hai, cioe` un
membro, che so, 
inline T& SharedVecRef<T>::ref() { return operatorT&(); }
e potrai scrivere
    vec[i].ref().f();
Non c'e` modo di farlo senza un qualche aggravio sintattico
perche` non puoi overridare l'operatore "punto"; almeno, in
questo modo, puoi farlo entro un solo statement e senza il
caos di uno static_cast...
Alex

 Alex
Alla fine ho risolto in un'altro modo. Sono andato a vedere come
e' stato implementato l'operatore [] in <vector> e' ho fatto cosi:
template <T> class SharedVec
{
   T *memory;  
   [...]
   typedef T& reference ;
   typedef const T& const_reference;
   reference operator[] (int n) { return *(memory + n); }
   const_reference operator[] (int n) const { return *(memory + n); }
};
Grazie comunque
Fabio

 };
Non sarebbe meglio controllare che la memoria indirizzata appartenga
realmente al tuo vettore?  Specialmente se ci _scrivi_, meglio evitare
un'address violation...
-- 
Maurizio Loreti                      
http://wwwcdf.pd.infn.it/~loreti/mlo.html
Un. of Padova, Dept. of Physics - Padova, Italy          
lor...@padova.infn.it


Hai perfettamente ragione, e in realta' (nel sorgente originale) controllo che
l'indice sia compreso tra 0 e il numero di righe allocate meno una, e in caso 
contrario faccio una throw. Solo che nel posting ho limitato il sorgente al minimo
e ho eliminato questa roba...
- Fabio

    typedef const T& const_reference;
Francamente mi sfugge l'utilità di creare nomi di tipi così lunghi come
sinonimi di nomi molto più concisi e chiari.
EMG

...

La libreria standard del C++ lo fa regolarmente (ogni allocatore DEVE,
a norma di standard, definire i tipi reference, const_reference, pointer,
ecc; ogni contenitore parametrizzato da un allocatore deve usare e
riesportare quelli del suo allocatore, e ogni contenitore non il tal modo
parametrizzato deve definirli; e cosi` via) allo scopo di permettere ad
un template di essere completamente generico, usando a sua volta
questi identificatori, invece di legarsi inutilmente a "T&", "const T&",
e cosi` via.
Ad esempio (non scritto al meglio, ma cerco di tenerlo semplice):
template<typename Container>
bool tuttiEguali(const Container& c)
{
    typename Container::const_iterator cit = c.begin();
    while(cit != ci.end()) {
        typename Container::const_reference cr = *cit++;
        if(cit != ci.end() && cr != *cit) return false;
    }
    return true;
}
Questo template e` applicabile a qualsiasi "Container" che
definisca: un tipo const_iterator; membri begin() ed end()
che tornano un tipo const_iterator, tipo che deve avere un
copy ctor accessibile, un applicabile operator!=, un
normale post-incremento, e un operator* unario che
torna qualcosa di assegnabile, e confrontabile per !=,
da un altro tipo const_reference che deve equalmente
essere definito dal "Container".
Sarebbe un peccato introdurre, senza ragioni, ulteriori
restrizioni su questo template, come il fatto che il
Container debba avere un value_type, che si possa
usare specificamente const value_type& per tenere
cio` che torna il dereferencing di un iteratore, e cosi` via.
Filosofia del generic programming e` la generalita`
_sostanziale_ ottenuta a prezzo (ammettiamolo!-)
di dover seguire un po' di "convenzioni" (formali), il
che tutto sommato non costa poi mai altro che un tot
di "codice boilerplate".  Fra queste convenzioni, la
libreria standard del C++ introduce appunto i vari nomi
di tipi come const_reference e cosi` via.
(Un template _veramente_ generale farebbe inoltre
uso del cruciale idioma "traits" per ulteriore ampiamento
dell'universo dei contenitori gestibili, ma questo e` in
pratica solo un uso particolare importante della pattern
"adaptor", generalizzato a quei casi di contenitori in
cui gli stessi NON sono classi sotto il nostro controllo
e quindi non si puo` fare affidamento su typedef "al
loro interno", eccetera -- il "caso di default" di un traits
e` pur sempre progettato per fare affidamento sulle
convenzioni di nome cui sopra accennavamo).
Alex

 ecc; [...]
Ok, grazie della spiegazione.
EMG