Newsgroup: it.comp.lang.c++
Titolo: Overloading di Funzioni

Saluti,
    premetto che è la prima volta che scrivo e seguo questo newsgroup,
quindi non vogliatemene se involontariamente la mia domanda è già stata
fatta.
Allora ho il seguente codice c++:
#include <iostream.h>
class Super {
 public:
        virtual void scrivi(Super *S) { cout << "super" << endl; }
};
class Sub: public Super {
 public:
        virtual void scrivi(Sub *s) { cout << "sub" << endl; }
};
main() {
 Sub *s=new Sub();
 Super *S=new Super();
 s->scrivi(S);
}
Il codice è estremamente semplice, ma in compilazione nasconde un
(credo) profondo dilemma.
Allora, cominciamo con l'errore che mi restituisce il compilatore ( egcs
2.95):
"poli.cc: In function `int main()':
poli.cc:18: type `Sub' is not a base type for type `Super'   "
Io so che l'istanza s ha a disposizione due metodi, uno scrivi(Sub *s),
messo a disposizione dalla classe Sub
e l'altro scrivi(Super *S) messo a disposizione dalla classe ereditata
Super.
Perchè mi da quell'errore in compilazione???
Piccola modifica:
#include <iostream.h>
class Super {
 public:
        virtual void scrivi(Super *S) { cout << "super" << endl; }
        virutal void pippo(Super *S) { cout << "pippo" << endl; }
};
class Sub: public Super {
 public:
        virtual void scrivi(Sub *s) { cout << "sub" << endl; }
};
main() {
 Sub *s=new Sub();
 Super *S=new Super();
 s->pippo(S);
}
Chiaramente questo prog non mi da errore e mi mette in output "pippo",
come volevasi dimostrare la presenza di due metodi per ogni oggetto di
tipo Sub.
Grazie
Ciro

 tipo Sub.
Qui chiami il metodo   'Super::pippo(Super *S)'   che appartiene solo alla
classe Super, percui ti e' consentito fare   's->pippo(S);'  (ovvero S =
s;).
Puoi far funzionare   'scrivi'   cosi':
(non serve alcun parametro, in realta')
#include <iostream.h>
class Super {
 public:
        virtual void scrivi() { cout << "super" << endl; }
};
class Sub: public Super {
 public:
        void scrivi() { cout << "sub" << endl; }
};
main()
{
    // Allocazione
    Sub *s = new Sub();
    Super *S = new Super();
    // Prova di esecuzione
    S->scrivi();    // out:  super
    s->scrivi();    // out:  sub
    // Ehm... questo e' piuttosto importante...
    delete s;
    delete S;
}
Al

 Perchè mi da quell'errore in compilazione???
A causa dell'hiding.
L'altro esempio non ha nulla a che vedere poiche` non vi e`
ricoprimento di nomi e dunque neppure "nascondimento".
Alex


E allora??? non posso overloadare funzioni???
Cmq copia il parametro S di s->pippo(S) nel parametro formale
Super::scrivi(Super *S);
ossia S=S;
Ciao
Ciro

 Alex
Ciao
Ciro

 Perchè mi da quell'errore in compilazione???
Perché non esisteva un Sub::scrivi(Super *) e il compilatore non può
convertire un Super * in un Sub *.
EMG

 questo effetto di hiding?
Linguaggio diverso, regole diverse.  In Java non si seguono
mai le classiche regole dell'annidamento lessicale: in un blocco
piu` interno non puoi MAI nascondere un nome piu` esterno.
Il C++, invece, segue strettamente le stesse regole del C da
questo punto di vista.
Prova:
    {
        int i = 23;
        // chiama opportune funzioni secondo il
        // linguaggio per mostrare i
        {
            int i = 45;
            // mostra i
        }
        // nuovamente mostra i
    }
In C, C++ (o Pascal, Modula-3, ecc -- sostanzialmente quasi
tutti i linguaggi con struttura lessicale a blocchi, indipendentemente
dai dettagli di zucchero sintattico ad essi relativi) vedrai
23, 45, 23; il nome i nello scope interno nasconde (solo
entro quello scope) il piu` esterno.
In Java, invece, questo costrutto e` considerato ERRORE.
Non puoi dunque trarre paralleli fra il Java da una parte e
tutti gli altri linguaggi a "lexical scoping" dall'altra.
Alex

 funzioni in C++...
In teoria potresti ottenere l'effetto che desideri, aggiungendo 
 using Super::scrivi
nella dichiarazione della classe Sub.
Sfortunatamente con gcc questa applicazione di "using" non è disponibile
(bug documentato, v. 
http://gcc.gnu.org/bugs.html
).
Max

}
Questo a poco a che vedere con il caso precedente, *s possiede
(ed e` visibile) un metodo pippo con signature compatibile.
Comunque resta sempre il problema dell'hiding (Sub::scrivi).
Paolo
--------------------------------------------------
  Paolo Marotta
  Computer Science, University of Pisa
  home:   
http://www.cli.di.unipi.it/~marotta
--------------------------------------------------

 ossia S=S;
Cosa c'entra s->pippo(S)  con  Super::scrivi(Super *S) ??? Sono 2
funzioni distinte. E poi, se passi a  pippo(Super *S) un puntatore
ad una classe Super, per forza che funziona!!!
P.S.: ma l'hai provato il secondo programma? A me, compilando con
egcs-2.91.57, si offende un po' quando trova
    virutal void pippo(Super *S) { cout << "pippo" << endl; }
    ^^^^^^
Al

 Alex
Ti ringrazio... ho capito perfettamente.
Ciro

 Al
egcs-2.91.66 non da nulla...
Ciro

 EMG
Ciro

 --------------------------------------------------
Ciao
Ciro

 Perchè mi da quell'errore in compilazione???
Attenzione che l'errore e' specifico della linea che ho indicato con
(1). Per decidere quale funzione chiamare il C++ percorre gli scope dal
piu' derivato attraverso la catena di eredita' finche non ne trova uno
contenente una funzione col nome richiesto. Li' si ferma e cerca una
funzione i cui parametri corrispondano a quelli della chiamata che cerca
di risolvere.
Nel tuo caso si ferma alla classe Sub perche' contiene un metodo scrivi
e siccome questo prende un parametro di tipo Sub, al quale il parametro
di tipo Super che tu passi non e' convertibile, segnala l'errore.
Come ti e' stato segnalato, per ottenere il comportamento che desideri
e' sufficiente che all'interno della tua Sub inserisci l'istruzione
using Super::scrivi;
che "inietta" nello scope della classe Sub tutti gli elementi denominati
scrivi della classe Super (nei compilatori in cui questa istruzione non
e' accettata e' di solito sufficiente ridichiarare il nome omettendo
using).
Ciao,
Nicola

Il C++ e invariante.
????
Una cosa e la covarianza, un'altra la controvarianza.
Il C++ implementa una controvarianza parziale e
(anche se da solo una warning) non puoi fare quello
che avevi in mente. Il tipo dei parametri di un metodo
non puoi cambiarlo a piacimento, puoi modificare il tipo
di ritorno se esso e` un puntatore (reference) a classe base...
In questo caso puoi trasformarlo in puntatore (reference) a
classe derivata.
Paolo
--------------------------------------------------
  Paolo Marotta
  Computer Science, University of Pisa
  home:   
http://www.cli.di.unipi.it/~marotta
--------------------------------------------------

 Sicuro di quel che dici???
Di sicuro c'è solo la morte (almeno ancora per un po') comunque mi
sembra di si, nei limiti del ragionevole. Si può, in generale, tentare
un upcast ma il downcast è normalmente senza senso. Se ho sbagliato
fammelo sapere.
EMG

 Il C++ e invariante.
Che intendi esattamente? Che non ha automatismi per quanto riguarda la
varianza?
EMG


Aggiungo che un sistema che ottiene lo stesso effetto di using e'
class Sub : public Super
{
  public:
    virtual void Scrivi(Super *S) { Super::Scrivi(S); }
    virtual void Scrivi(Sub *S) { ... }
};
La spiegazione data dell'hiding in "The C++ programming Language" non
mi ha convinto molto se devo essere sincero: si parla del fatto che si
rischierebbe di invocare metodi che non si sapeva di aver ereditato
quando magari ci si aspettava una conversione automatica sui parametri.
In altre parole uno chiama a->pippo(x) con x intero illudendosi che
venga chiamato A::pippo(double) e invece viene chiamata un metodo che
non si sapeva esistere definito nel padre del padre del padre della
classe A.
In ogni caso la cosa, sebbene a volte fastidiosa, e' presente e
richiesta dallo standard.
----------------------------------------------------------------
 <\___/>  Andrea        email: agriff at tin dot it
 / . . \  "6502"          irc: _6502_@EFNET#italia
(  (")  ) Griffini   homepage: 
http://www.netservers.com/~agriff
Sent via Deja.com 
http://www.deja.com/
Before you buy.

 Paolo
Scusami tanto ma non capisco proprio quello che vuoi dire.
Se la implementa non deve dare warning... da warning... così...
a caso o perchè gli stai antipatico???
Non posso cambiare il tipo dei paramentri???? Allora per te
non posso "overloadare" (perdona il mio spaghetti english)
le funzioni???
Ciro

 EMG
Ciro

 Nicola
Ciao
Ciro

 EMG
Ciro

 classe A.
Si questo è un problema...
Ed è questo che sto cercando di risolvere.
Ciro

Ciro


Max M mi ha fatto notare che è un bug documentato del gcc.
allora prova il vecchio metodo:
Super::scrivi;
Paolo


 };
Mi spieghi meglio? Dov'č esattamente la controvarianza in questo
esempio?
EMG

esempio?
I termini "covarianza" e "controvarianza" sono normalmente riferiti a 
come variano il tipo di ritorno e i tipi dei parametri di una funzione
l'uno rispetto agli altri.
Nella controvarianza i tipi dei parametri possono appunto essere
modificati in favore di supertipi, mentre il tipo di ritorno puo'
variare a favore di sottotipi (nell'esempio da T1* a T2*), in tal senso
si parla di contro-varianza.
Nel caso del C++ la regola di controvarianza e' limitata ai tipi 
ritornati dalle funzioni e solo nei casi di puntatori e reference. Per
questo si parla di controvarianza parziale.
Nella covarianza i tipi possono variare tutti nella stessa direzione
(co-varianza) e se non sbaglio questa e' la via seguita da Eiffel.
Paolo

 (co-varianza) e se non sbaglio questa e' la via seguita da Eiffel.
Esatto (ed e` matematicamente incoerente per i tipi degli ARGOMENTI,
benche` matematicamente corretta per quelli RITORNATI).
Sather, una variante di Eiffel sviluppata a Berkeley, e` coerentemente
e pienamente aderente alla matematica dei tipi: co-variante per i tipi
ritornati, contra-variante per quelli degli argomenti.
Sostanzialmente lo stesso risultato di Sather ma per altre vie (poiche`
i modelli di tipi sono diversi) si ottiene in linguaggi a tipizzazione
dinamica (a signature) come Smalltalk e Python, o in linguaggi ancora
diversi (e, come Sather, assai poco noti, e sostanzialmente usati nel
mondo accademico, pero` molto interessanti) come Dylan, Mozart, ML.
Alex

 in standard C++, *NON* CONTRA-varianti.
Infatti mi pareva...
EMG


In letteratura ho sempre trovato il termine contro-varianza (ricordo
sicuramente almeno un articolo di C. Pescio)
riferito non al modo in cui il tipo di un metodo varia in relazione
alla classe, ma al modo in cui varia in relazione ai tipi dei parametri.
In tal senso contro-varianza, nel senso di "il primo (tipo della 
funzione) in una direzione gli altri nella direzione opposta.
Paolo


In Visual C++ 6.0 non compila, dicendo che non la ffunzione virtuale che fa
l'overriding
differisce solo per argomento di ritorno, e questo non gli piace.
Pero' mi domando: che differenza c'e', nel codice sopra scritto, tra
scrivere:
struct sopra: sotto {
        virtual sopra* lafunz();
    }
e scrivere:
struct sopra: sotto {
        virtual sotto* lafunz();
    }
In classi con metodi puramente virtuali non dovrebbe fare nessuna differenza
usare il puntatore alla classe base o il
puntatore alla classe derivata.



 usare il puntatore alla classe base o il puntatore alla classe derivata.
La "struct sopra" puo` AGGIUNGERE metodi (anche se virtuali) alla "struct
sotto" da cui eredita, e quindi un "puntatore-a-sopra" puo` essere usato
in modi in cui un "puntatore-a-sotto" NON potrebbe (non viceversa).
In C++ non-standard, come ad es. in VC++ (almeno sino alla 6) occorre un
cast per rimediare al problema; lo standard elimina la necessita` di
questo cast, grazie alla covarianza dei valori di ritorno dei metodi
virtuali, che esso consente.
Alex