Newsgroup: it.comp.lang.c++
Titolo: Classi data, ora e dataOra

Devo maneggiarte un po' di tempi e pensavo quindi di scrivere delle
classi adatte allo scopo. Inizialmente avevo pensato di scrivere
una classe data, una ora, e una dataOra che ereditasse da entrambe.
Con questo metodo avrei probabilmente usato degli int per la
memorizzazione di campi come minuti secondi eccetera, mentre poi
ho pensato alla famosa unix epoch ovvero ai secondi passati dal
1970. Usando un campo time_t nella classe dataOra sarebbe
tutto molto piu' comodo perche' userei le librerie C in time.h,
pero' poi che rapporto dovrei creare tra la classe ora e dataOra?
Insomma ho un dubbio "filosofico" su come strutturare classi di
questo tipo...
-- 
Saluti,
 Francesco Cipriani

Francesco Cipriani <f...@news.localhost> wrote in message
news:c1.2b8.2TvnHZ$1...@news.nwobbs...

Probabilmente la cosa migliore e` fare delle data e ora delle
interfacce astratte, invece che vincolarle a una particolare
implementazione.
class data {
public:
    virtual int giornoDelMese() const = 0;
    virtual int meseDellAnno() const = 0;
    virtual int anno() const = 0;
};
class ora {
public:
    virtual int h() const = 0;
    virtual int m() const = 0;
    virtual int s() const = 0;
};
class dataOra: public data, public ora {
    time_t tt;
public:
    int giornoDelMese() const ;
    int meseDellAnno() const ;
    int anno() const ;
    int h() const ;
    int m() const ;
    int s() const ;
    // snip: ctor e metodi per settare tt
};
int dataOra::s() const {
    return gmtime(&tt)->tm_sec;
}
// ecc, ecc
(Ovviamente puoi fare un'implementazione piu`
furba, che chiami gmtime UNA sola volta quando
tt viene settato/modificato e usi una struct tm ove
copiare il risultato della detta gmtime, come "cache"
o memoizing che dir si voglia).
Alex

implementazione.
Si ok, pero' io voglio che un oggetto data o ora sia istanziabile,
e con classi virtuali pure mi attacco.. e se per esempio mettessi
i campi int di ora, per esempio, come privati in pratica potrei
usare la classe ora, ma quella dataora che deriverei non avrebbe
piu' quei campi e ridefinirei le funzioni che accedono ai campi..
che la trovi come soluzione? Tra le altre cose non so bene cosa
succede quando si deriva una classe con campi private: non
vengono piu' presi in considerazione dalle classi derivate o
vengono allocati ma nascosti? (cosa che mi sembrerebbe un po'
strana visto che non c'e' modo di utilizzarli).
In pratica io direi di fare:
class ora {
public:
    virtual int h() const { return h; };
    virtual int m() const { return m; };
    virtual int s() const { return s; };
    // costruttori vari
private:
    int h, m, s;
};
e poi il resto come hai scritto tu, cioe' vengono rifefinite
le funzioni virtual di cui sopra nella classe derivata.
-- 
Saluti,
 Francesco Cipriani

 strana visto che non c'e' modo di utilizzarli).
Sono allocati eccome, e sono visibili dai metodi della classe
in cui sono dichiarati (e dai friend di essa).  Che vuol dire
"non c'e` modo di utilizzarli"?  Se i metodi della classe in
questione (e/o friend) li usano, c'e` modo eccome.  Se poi
causa override ti ritrovi privo di accesso a questi campi, beh,
e` solo uno dei tanti difetti dell'eredita` di classi concrete.
E` quasi impensabile che un compilatore si metta a scoprire
se possa mai trovarsi in situazioni in cui si accede a quei
campi (ad esempio se un qualche override di un metodo
pippo in una classe derivata chiami nella propria stessa
implementazione anche base::pippo, com'e` assai comune).
D'altra parte, quand'anche questa situazione si trovasse ad
essere verificata per un certo set di implementazioni di tutti
i metodi in questione, basterebbe cambiarne anche solo una
(magari in un'altra classe derivata sei o sette livelli sotto...)
per rompere tutto.  Il normale meccanismo di compilazione
separata del C++ non permette di compiere delle ottimizzazioni
cosi` fragili.
Alex

"non c'e` modo di utilizzarli"?  Se i metodi della classe in
[..]
Intendevo dire utilizzarli dalle classi derivate, e' chiaro che
qualcuno li puo' utilizzare :). Quindi, riassumendo, per
istanziare la mia bella classe data, tu creeresti la classe
virtuale pura data e poi ne deriveresti una diciamo "dataInt"
che memorizza i dati in degli int?
-- 
Saluti,
 Francesco Cipriani

 "non c'e` modo di utilizzarli"?  Se i metodi della classe in


Bene :-), e' quello che invece mi hanno fatto vedere all'universita' :)
All'universita` fanno vedere molte cose, il guaio e` che scelgono sempre
esempi minimali che poi non approfondiscono. Classico esempio
e` quello dello studente lavoratore, che sembra essere perfetto mentre
ha un gravissimo errore di fondo... usa l'ereditarieta` (che e` una
condizione permanente) per modellare una relazione temporanea.
In linguaggi OO meno convenzionali potrebbe magari funzionare, ma
normalmente non e` cosi`.
Comunque ereditare l'implementazione non e` sbagliato a priori, in
alcuni casi puo` essere conveniente. Bisogna pero` stare attenti
perche` pone maggiori vincoli (grado di accoppiamento) tra le
classi imparentate.
Paolo
--------------------------------------------------
  Paolo Marotta
  Computer Science, University of Pisa
  home:   
http://www.cli.di.unipi.it/~marotta
--------------------------------------------------

questo tipo...
Potresti crearti delle classi astratte per la data e l'ora da cui poi
derivare possibili implementazioni. Il modello da seguire sarebbe
quello proposto da Alex Martelli.
Per quanto riguarda la classe DataOra potresti agire cosi`
class DataOra {
   public:
      DataOra(Data* d, Ora* o) /* ... */;
      int GiornoDelMese() {
         return DataPtr->GiornodelMese()
      }
      /*  ... */
  private:
     Data* DataPtr;
     Ora* OraPtr;
};


 che memorizza i dati in degli int?
Si`, in generale svilupperei la _progettazione_ del sistema
(o sottosistema) in termini di "classi astratte pure" (ovvero
"interfacce" o "classi-protocollo"), eventualmente con le
"envelope" per incapsularle se e` importante che possano
essere istanziate, passate per valore, assegnate.
La *implementazione* dello schema di progetto puo` in
certi casi usare (piu` che altro per comodita`) anche la
eredita` di implementazione, ma normalmente si puo`
farla almeno altrettanto bene con "contenimento e
delegazione".
Problema tipico del "contenimento e delegazione" in C++
e` la necessita` di scrivere non poco "codice boilerplate"
(ripetitivo, privo di difficolta` o di particolare contenuto,
ma nondimeno da scrivere e manutenere) poiche` il
linguaggio manca di qualsiasi concetto di "delegazione
generica e/o implicita" se non quello che gli deriva dalla
eredita` d'implementazione.  Il generic-programming qui
puo` aiutare, anche se tipicamente al (modesto) prezzo
di dovere inserire ogni tanto un esplicito "->" (infatti e`
proprio "operator->" uno dei pochissimi modi in C++ di
ottenere una delegazione generica...!).
(Notiamo, ad onor del vero, che anche gli altri elencati
linguaggi "compile-time type-safe" sono decisamente a
corto di meccanismi di delegazione, offrendo palliativi
di potenza grosso modo simile a quella dell'operator->
del C++; solo i linguaggi piu` dinamici, come Smalltalk,
CLOS, e Python, dispongono in pratica di meccanismi
veramente potenti di delegazione, o attraverso le gia`
nominate meta-classi, ovvero piu` semplicemente con
schemi come __getattr__ in Python e simili -- il che e`
buffo, considerando che in tali linguaggi non vengono
formalizzati, come gia` accennato, gli usi delle "pure
interfacce"...).
Alex

 classi imparentate.
Questo e` il punto-chiave, ma ce ne sono altri.  Implementare in
modo corretto un operator= su classi concrete che ereditano l'una
dall'altra, ad esempio, puo` spesso essere quasi impossibile!  Se
si vuole che la classe concreta che fa da base sia "canonica" (cioe`,
tra l'altro, che abbia un copy assignment, i.e. operator=, che sia
pubblicamente accessibile), bisogna trovare un modo sensato
di implementare:
    derived& derived::operator=(const base& rhs);
(il tipo di ritorno puo` anche essere void, non cambia).  Cioe`
un derived deve poter venire assegnato da un base e "inventarsi"
in qualche modo, "al volo", tutta quella parte del suo stato che
e` addizionale rispetto allo stato di una "base nuda e cruda".
In certi casi si ha fortuna, e la semantica dello specifico dominio
applicativo offre una interpretazione naturale e sensata di questa
operazione; anche qui ci vuole MOLTA cautela, perche` e`
indispensabile verificare se rhs non sia in realta` un derived,
ma in questi casi la cosa e` fattibile.  In tanti altri, pero`, no.
Un esempio del caso in cui e` fattibile:
    class RealNumber {    // numero a precisione illimitata
    public:
        // snip snip
        RealNumber(double x=0);
        virtual RealNumber& operator=(const RealNumber& rhs);
        // se qualcosa e` virtual, che lo sia anche il dtor...!
        virtual ~RealNumber();
    };
    class ComplexNumber: public RealNumber {
        RealNumber imaginaryPart;
    public:
        virtual ComplexNumber& operator=(const ComplexNumber& rhs) {
            RealNumber::operator=(rhs);
            imaginaryPart = rhs.imaginaryPart;
            return *this;
        }
        virtual ComplexNumber& operator=(const RealNumber& rhs) {
            const ComplexNumber* p=
                dynamic_cast<const ComplexNumber*>(&rhs);
            if(p) return operator=(*p);
            RealNumber::operator=(rhs);
            imaginaryPart = RealNumber(0.0);
            return *this;
        }
        // snip snip di tutto il resto
    };
Raramente si trova un caso piu` atipico di eredita` d'implementazione,
in cui cioe` essa si presenta cosi` "liscia" e semplice come in questo.
Nonostante questo, sarebbero frequentissimi (lo dico giudicando in
base alla mia esperienza) errori clamorosi, come non rendere virtuali
l'operator=, ovvero il distruttore, o non testare correttamente
nell'operator= di ComplexNumber con dynamic_cast, eccetera.
Notiamo comunque che e` pur sempre indispensabile che la classe
concreta che deve fare da base sia _progettata allo scopo_, con il
necessario complemento di funzioni virtuali.  Altrimenti, il risultato
non puo` che essere disastroso.
Per esempio, ereditare, che so, da std::vector, che NON e` fatto
per servire come classe-base e quindi di virtual non ne ha, e` un
_grave_ errore di progetto.
Queste riflessioni si applicano naturalmente con piena forza solo
all'eredita` _pubblica_.  L'eredita` privata, e quello strano ibrido
che e` l'eredita` protetta, soffrono assai meno di questo tipo di
problemi (mentre quelli di "coupling" esagerato, che Paolo pure
nota, tendono a restare tutti), perche` si ha pieno controllo (nel
caso dell'eredita` privata) di tutto il codice che puo` "far uso"
del fatto che stiamo ereditando, o almeno (nel caso della
eredita` protetta) si puo` e deve documentare rigorosamente
il protocollo d'uso (e il codice-cliente che _eredita_, e quindi ha
accesso alla base da cui ereditiamo in modo protetto, gia` deve
accettare tutto il coupling ed i vincoli che da esso discendono).
In pratica, naturalmente, non c'e` ragione di non usare invece
quello che costituisce quasi l'unico uso _sensato_ dell'eredita`
pubblica di implementazione, cioe` il "pattern without a name"
identificato da Coplien:
    class derivata: public base<derivata> { // ... ecc, ecc
Questa cruciale sinergia fra il generic programming e la
eredita` d'implementazione permette di ottenere tutto il
desiderato "riuso di codice, con riduzione di boilerplate"
riducendo al minimo i problemi tipici dell'eredita`.
Alex

condizione permanente) per modellare una relazione temporanea.
Caspita, devo dire che forse ci sarei cascato pure io :), perche' ad
esempio se uno disegna uno schema e-r (non so se conosci la progettazione
di db) in effetti puo' inserire lo studente lavoratore come sottoinsieme
di quello non lavoratore. E quindi in C++ come si farebbe? Con la
composizione? Pero' in effetti oggi leggevo che uno puo' definire
una gerarchia se il tipo della classe derivata e' anche un tipo
della classe base (se non ho capito male questo indica il rapporto
is-a) e lo studente lavoratore e' uno studente.. uhm.. spiega un po'
meglio, please
-- 
Saluti,
 Francesco Cipriani

                dynamic_cast<const ComplexNumber*>(&rhs);
Devo ancora vedere bene cosa fa il dynamic_cast, (devo capire che
differenza c'e' con una cast "classico" tipo (const ComplexNumber*)&rhs
che pero' non so nemmeno se si puo' fare), ma in questo caso, castando
il tipo base RealNumber& a quello derivato che valori hanno i campi
dell'oggetto ComplexNumber in cui viene trasformato il RealNumber?
Vengono chiamati i costruttori di default dei data members?
-- 
Saluti,
 Francesco Cipriani

era appunto questo che chiedevi, se venivano eliminati...
Si, pero', ritenendo che il compilatore fosse dotato di intelligenza
quasi umana :) pensavo che quando la classe derivata ereditava da quella
base i campi effettivamente allocati - quando si doveva allocare un
oggetto della classe derivata - fossero solo quelli effettivamente
usabili dalla classe derivata, ma evidentemente non ho chiaro come
funziona l'allocazione, che comunque da quanto mi dici si comporta
sempre allo stesso modo, ovvero allocando tutto quello che c'e' da
allocare (anche perche' in effetti una classe derivata puo' usare
i membri privati di una base usando i metodi di quella base che li
hanno nel loro scope).
-- 
Saluti,
 Francesco Cipriani

class dataOra: public data, public ora {
[...]
Cosi' facendo quindi potrei creare un'altra classe derivata
OraConcreta : public ora che implementerebbe in modo diverso
la gestione dei vari campi, indubbiamente e' interessante..
Pero' poi si potrebbe fare "ora *do = new dataOra;" giusto?
Il che sarebbe un po' strano, visto che dataOra e' in realta'
un'estensione di ora e non un'implementazione concreta.
Insomma non capisco bene perche' dataOra dovrebbe ereditare
da ora, o meglio non capisco quali sarebbero i vantaggi
rispetto ad esempio non ereditare ora in dataOra e implementare
delle funzioni analoghe a quelle di ora in dataOra.
-- 
Saluti,
 Francesco Cipriani

         return DataPtr->GiornodelMese()
Il fatto era che volevo implementare la gestione di dataOra con un
campo unsigned long lasciando perdere l'implementazione di data e
dir ora, che pero' sarebbero state comode per oggetti strettamente
di quel tipo. Cosi' indubbiamente funzionerebbe.. boh al massimo
aggiungo un campo unsigned long, non e' tutto questo spreco :-),
solo che dopo devo fare doppi aggiornamenti
-- 
Saluti,
 Francesco Cipriani

meglio, please
Il problema e` che lo studente-lavoratore non sara` sempre uno studente,
un giorno la condizione di studente cessera` di esistere. L'oggento non
potra` piu` essere una istanza di studente (ma lo sara` ancora per la
classe lavoratore). In un linguaggio come il C++ l'ereditarieta` modella
una relazione permanente e quindi per riflettere la nuova situazione
dovresti distruggere il vecchio oggetto e crearne uno nuovo solo
nella classe lavoratore. Tutto questo cozza contro il fatto che
l'identita` e` unica e immutabile... il nuovo oggetto sarebbe
qualcosa di completamente diverso che in comune col vecchio
ha solo lo stato. E` come se cambiassi la chiave primaria
di un record... e in effetti in un db a oggetti l'identita`
e` una chiave primaria!
La soluzione consiste nel modellare la condizione di studente o
lavoratore tramite contenimento anzicche` ereditarieta` definendo
una classe ruolo di cui ogni oggetto possiede una istanza.
Questo vale in generale per tutte le relazioni temporanee.


 Vengono chiamati i costruttori di default dei data members?
Un cast di *puntatori* (qui casto l'INDIRIZZO, nota bene, NON
il valore!!!) non ha MAI come effetto la "costruzione" di alcunche`,
ne` usando il cast dinamico, ne` la forma obsoleta di cast.
Alex

 solo che dopo devo fare doppi aggiornamenti
Appunto -- EVITA di duplicare dati se non come specifica ottimizzazione,
se no ti esponi a Nmila problemi.
Ereditando classi puramente astratte e implementando per
incapsulamento (o eredita`-piu`-genericita`, con la famosa
"pattern without a name" di Coplien) non hai questi problemi.
Alex

 delle funzioni analoghe a quelle di ora in dataOra.
Ereditare da una classe astratta offre il vantaggio fondamentale:
il POLIMORFISMO dal punto di vista del cliente.  Ovvero, chiunque
implementi una classe X che deriva dall'interfaccia Y puo` essere
passato (per riferimento o via puntatore) a codice cliente che si
aspetta una Y e fa uso della Y, *polimorficamente*.
Alex

 meglio, please
Sembrerebbe che "lavoratore" sia più un attributo dello "studente" che
non un tipo particolare di questo. Quindi si potrebbe aggiungere allo
"studente" un membro "bool lavoratore" ma credo che questo sia troppo
limitativo e poco OO.
EMG

 hanno nel loro scope).
Esatto. Bisogna solo precisare "...i metodi pubblici di quella base...".
EMG

 un'estensione di ora e non un'implementazione concreta.
Immagino che dataOra si intendesse come classe concreta derivata dalle
due classi astratte data e ora e non come classe astratta essa stessa.
EMG

 ne` usando il cast dinamico, ne` la forma obsoleta di cast.
Già, come non lo ha la semplice definizione di un puntatore
T *p;
Mentre invece, ad esempio, lo static_cast tra due tipi (non puntatori)
provoca la costruzione per copia.
EMG

 meglio, please
La pattern di analisi "Role" ("ruolo"), che e` chiaramente quella
che qui ci troviamo a riconoscere, non si modella bene con
l'appartenenza-a-classe (a causa dei suoi aspetti transitori).
Le scelte-base sono di due tipi: o una` entita`-permanente
ha-un oggetto-Ruolo (per-riferimento), e un oggetto-Ruolo
puo` appartenere a classi che ereditano molteplicemente da
tutti i possibili ruoli di interesse; ovvero, e non dovrebbe essere
difficile vedere quanto *enormemente* piu` semplice sia la
seconda scelta, una entita` ha-una *collezione* di Ruoli,
ciascuno dei quali appartiene a UNA classe che modella un
possibile ruolo di interesse.
Supponi ad esempio la seguente sequenza:
    1. Giovanni si iscrive ad un corso (ora G e`-uno Studente)
    2. Giovanni, continuando il suo corso, inizia anche a
        lavorare part-time, la sera, nell'agenzia che ha in
        appalto le pulizie della scuola (ora G e`-uno Studente
        Lavoratore)
    3. Giovanni lascia il lavoro e continua il corso (G e` pur
        sempre uno Studente ma non piu` Studente-Lavoratore)
Se l'entita` G ha-una _collezione_ di ruoli, questa semplice
sequenza e` modellata in modo talmente ovvio da essere
banale: la collezione dei ruoli di G cresce da 1 a 2 elementi
e poi torna nuovamente ad 1 perdendo il secondo dei due.
La continuita` del ruolo di G come studente (su cui non ha
alcun effetto l'assuzione come lavoratore e il successivo
lasciare il lavoro) e` un effetto clamorosamente ovvio della
strutturazione del progetto.
Se l'entita` G ha-un solo Ruolo (che puo` in un dato momento
appartenere a una classe che eredita molteplicemente), la
stessa sequenza pur nella sua semplicita` evidenzia gia` dei
problemi.  La identita` "intrinseca" dell'oggetto-Ruolo di G
cambia due volte; pero` dobbiamo pur sempre modellare il
fatto che dal "lato" Studente del ruolo, c'e` continuita` (G
e` iscritto allo stesso corso con continuita`); occorre dunque
uno sforzo supplettivo (un attributo "vera identita` nei vari
possibili sotto-ruoli" -- probabilmente una collezione...) per
modellare esplicitamente quello che e` invece "ovvio" nel
primo caso.
E se G ha _due_ lavori?  L'eredita` non ci aiuta minimamente.
La composizione (ha-una collezione) e` invece ovviamente del
tutto appropriata.
Piu` si approfondisce l'analisi, particolarmente per casi d'uso,
piu` emergono elementi che rendono appropriata la prima
scelta rispetto alla seconda...
Alex

 assolutamente, in C++).
Nel numero 6 di C++ Informer, Carlo Pescio presenta una tecnica che
chiama Meta-morfismo che, anche se con qualche limite di validità,
permette ad un oggetto di cambiare classe.
EMG

limitativo e poco OO.
Neanche questa e` una soluzione valida, la condizione di studente e`
generalmente temporanea, e anche se non e` desiderabile anche
quella di lavoratore lo e`. L'unica valida alternativa in C++ e` l'uso
del contenimento (eventualmente indiretto) e modellare le condizioni
di studente e lavoratore mediante opportune classi.


 Date l'ho riservato all'interfaccia.
Il mio gusto personale e` per IPippo come interfaccia,
PippoImpl per l'implementazione di IPippo (generalmente
un template), e Pippo senza decorazioni per la classe
concreta; mi pare di averlo visto per la prima volta in
un testo di Yourdon e Coad, che a loro volta davano
credito ai progettisti della Microsoft per lo stile "IPippo"
tipico dei nomi delle interfacce COM (negli strumenti MS
pero` la classe concreta tende ad essere CPippo, non
solo Pippo, e la classe intermedia implementativa di solito
IPippoImpl, non PippoImpl).
L'alternativa, stile Java, e` di usare per l'interfaccia un
aggettivo piu` descrittivo -- a volte funziona molto bene
(ad esempio interfacce come Printable, Drawable,
Persistable, ecc) ma spesso porta invece a risultati
goffi (particolarmente problematico in inglese lo
spelling in certi casi -- "Serializable" o "Serializeable"?-).
Alex

 Date l'ho riservato all'interfaccia.
Potresti usare IDate e ITime per le interfacce e Date e Time per le
classi concrete.
EMG

              LDateTime
Io modellerei LDateTime utilizzando il contenimento indiretto,
al costo di un po' di funzioni di forward verso Date e Time
avresti una unica classe che puo` utilizzare qualsiasi oggetto
derivato da Date e Time:
class DateTime {
   public:
      DateTime(Date* d, Time* t);
      /* ... */
   private:
      Date* dt;
      Time* tm;
};
La tua soluzione ti obbliga invece
a derivare nuove classi DateTime se decidi di utilizzare
implementazioni diverse di Date e/o Time.


implementazione).
Pero' se si usa l'ereditarieta' virtuale (virtual public) si
puo' fondere il longint usato dalle due classi padre e siamo
a 12 byte :). Interessanti comunque tutte le altre osservazioni
che poi sperimentero' con piu' calma, grazie.
-- 
Saluti,
 Francesco Cipriani

Non c'e` alcuna classe base comune, quindi e` inutile, anzi...


 non potrebbe esserlo?
Perche` non implementa TUTTI i metodi virtuali puri che
dichiara (nessuno) o deriva (quelli di class Time).
Alex

dichiara o deriva.
Gia', non avrei dovuto nemmeno chiederlo :)
Ora ho finalmente implementato questi benedetti date e datetime con il
metodo del simil-PWAN ma vorrei definire degli operatori, tipo < > ==
e compagnia. Visto che DateTimeImpl usa lo stesso longint di GenericDate,
volevo definire questi operatori a livello di GenericDate in modo da
eriditare anche quelli, ma mi sono chiesto cosa mettere nei parametri
formali di tali operatori. Andrebbe bene una cosa tipo
template <typename Interfaccia>
GenericDate<Interfaccia>::bool operator<(const GenericDate<Interfaccia>& date);
? Mi viene il dubbio perche' io vorrei usare gli operatori poi anche
passandogli un DateTimeImpl, sarebbe possibile?
-- 
Saluti,
 Francesco Cipriani

 passandogli un DateTimeImpl, sarebbe possibile?
Non mi e` chiarissimo cosa tu voglia fare, comunque ti
ricordo che una classe (anche una classe template) puo`
avere metodi che sono a sua volta dei template (anche
se in certi compilatori molto diffusi ci sono ancora dei
limiti su questo, tipo il fatto che vengono supportati solo
se inline -- ma a molti scopi puo` bastare).
Alex

Non mi e` chiarissimo cosa tu voglia fare, comunque ti
[..]
Dunque, riassumo un attimo. Al momento attuale ho la mia gerarchia
fatta cosi'
                     IDate
                     /    \
                    /      \
       GenericDate<IDate>   ITime
          (Date)              \
                              GenericDate<ITime>
                                \
                                DateTime
Sia GenericDate<Interfaccia> che DateTime usano una rappresentazione
interna longint peer il tempo. Quello che volio fare e' definire gli
operatori di confronto (< > == <= >=) sia per Date che per DateTime.
Al che mi chiedo: se definisco questi operatori in GenericDate<Interfaccia>
che parametri formali uso per gli operatori?
Se ad esempio scrivo
template <typename Interfaccia>
GenericDate<Interfaccia>::operator==(const GenericDate<Interfaccia>& par)
poi questo operatore, venendo ereditato da DateTime, sara' richiamato con
parametro di tipo DateTime, ma e' stato definito col parametro del tipo
che vedi sopra: funzionera' comunque anche per DateTime, visto che DateTime
eredita da GenericDate<Interfaccia>?
-- 
Saluti,
 Francesco Cipriani

 eredita da GenericDate<Interfaccia>?
Si`, il polimorfismo funziona: se un parametro e` dichiarato come
reference ad un MioTemplate<MioArgomento>, posso passare in
quel parametro qualunque oggetto che E`-UN MioTemplate<MioArgomento>
(e non vi sara` "slicing", grazie al passaggio by-reference), cioe`
qualsiasi oggetto di una classe che eredita, appunto, dalla
MioTemplate<MioArgomento>.
Alex

quel parametro qualunque oggetto che E`-UN MioTemplate<MioArgomento>
[..]
Infatti ha funzionato perfettamente. Pero' mi e' venuto un dubbio: gli
operatori vanno definiti a livello di interfaccia? In pratica, ora io
ho gli operatori definiti al livello di GenericDate<Interfaccia>, e non
posso fare una cosa del genere:
Date now;
IDate &d = now;
cout << (now == d);
perche' l'operatore == e' definito per GenericDate<Interfaccia> e
discendenti. Quello che ho scritto sopra e' anomalo, ma sarebbe quello
che succederebbe se venisse passato un Date a una funzione che ha
come parametro un Idate&
Il compilatore si lamenta che non e' definito l'operatore == tra Idate
e Date, visto che in effetti quello definito e' questo
(1)
template <typename Interfaccia>
GenericDate<Interfaccia>::operator==(const GenericDate<Interfaccia>& par)
E se definisco nella IDate
(2)
virtual operator==(const IDate& par) = 0;
poi chiaramente succede che quando GenericDate lo definisce come sopra (1)
l'operatore (2) rimane virtuale e l'oggetto Date non puo' essere istanziato.
Per far quindi funzionare l'operatore dappertutto, dovrei quindi definire
gli operatori a livello di IDate e definirli concretamente nelle classi
derivate sempre con gli stessi parametri, suppongo... Solo che qui c'e'
il fatto della derivazione templatizzata, e quindi come dovrei fare?
Cioe' che parametri metto per gli operatori?
(sperando di essermi spiegato)
-- 
Saluti,
 Francesco Cipriani

 (sperando di essermi spiegato)
Onestamente faccio molta fatica a seguire le tue spiegazioni, ed
e` probabile che io non le stia comprendendo appieno, ma spero
che le suddette considerazioni generali ti siano comunque utili.
Alex