Newsgroup: it.comp.lang.c++
Titolo: Riferimenti alle istanze e problemi di covarianza (forse)

Ciao a tutti, vengo subito al nocciolo della questione.
Poniamo di avere una classe base
struct base{
    base& foo(){
        // Fai qualcosa...
        return *this;
    }
    // ... altra roba
};
Con funzioni membro del tipo di foo() che, dopo aver svolto la propria
azione, restituiscono un riferimento all'oggetto sul quale sono state
istanziate.
Ora vogliamo estendere le funzionalità della classe mediante eredità,
rimanendo sempre nell'ambito della strategia "ogni funzione membro deve
restituire un riferimento all'oggetto sul quale è istanziata". Ciò è utile
per poter eseguire più azioni "concatenandole" (per esempio:
oggetto.fai_qualcosa(parametri).fai_qualcosaltro(parametri).fai_qualcosaltro
_ancora(parametri) ).
E' ora che sorge il problema : una volta derivata una nuova classe, si
vorranno aggiungere altre funzioni membro, e a questo punto la domanda è :
qual è il tipo preciso che dovranno restituire ? Il tipo della classe
derivata ? Il tipo della classe base ? In ogni caso il compilatore non ne
vuole sapere. Esempi:
// (1)
struct derived1:public base{
    derived1& foo1(){
        // Fai qualcosa...
        return *this;
    }
};
// (2)
struct derived2:public base{
    base& foo2(){
        // Fai qualcosa...
        return *this;
    }
};
Nel primo caso, scrivendo
derived1 d1;
d1.foo().foo1();
il VC6 mi dice " error C2039: 'foo1' : is not a member of 'base' ".
Nel secondo caso, scrivendo
 derived2 d2;
 d2.foo().foo2();
Ottengo lo stesso errore.
Ora, ho capito che il problema risiede nella covarianza dei tipi di ritorno
delle funzioni: in pratica, per le istanze per esempio di derived1 , le
funzioni membro che essa eredita da base dovrebbero trasformarsi
trasparentemente in modo che il tipo ritornato sia derived1& e non più base&
, e così anche per derived2.
So anche che il vc6 non supporta la covarianza dei tipi, ma ammesso che
questo sia il problema chiedo : c'è un modo per aggirarlo in modo che si
possano concatenare le chiamate a funzione indipendentemente dal fatto che
queste risiedano in classi base o in classi derivate ?
Ho provato anche con la tecnica del "polimorfismo statico" ma sorgono grossi
problemi (che si sono rivelati insormontabili) per catene di derivazione più
lunghe di 2.
C'è un modo per venirne fuori ? Spero di essere riuscito a spiegare la
questione, e ringrazio in anticipo per gli eventuali aiuti!
--
imho

 [CUT]...
Secondo me devi dichiarare le funzioni foo1 e foo2 in base come virtuali
ed il valore di ritorno deve essere base&. Nelle derivate le specializzi.
L'alternativa e' utilizzare i templates, ma e' tutta un'altra storia.
Ciao.
  -Ema-

 Ottengo lo stesso errore.
  Il problema non e' il tipo di ritorno di foo1 o foo2, ma di foo, il
quale ritorna un riferimento a base, classe che non ha ne' la funzione
foo1, ne' la funzione foo2. Il problema e' che vuoi mettere nelle classi
derivate funzioni che nella classe base non ci sono: altrimenti potresti
rendere la classe base un'interfaccia, con tutte le funzioni virtuali, e
poi le classi derivate implementano queste funzioni restituendo puntatori
alla classe base, nel qual caso funzionerebbe tutto.
  Il discorso della covarianza dei tipi di ritorno non c'entra, e' solo
quando implementi una funzione virtuale in una classe derivata.
_dave_

 quando implementi una funzione virtuale in una classe derivata.
E comunque in C++ si parla di controvarianza o ricordo male?
Ciao.
  -Ema-

 E comunque in C++ si parla di controvarianza o ricordo male?
A suo tempo feci molta confusione...
un po' di pseudocodice
class R1
class R2 subclass of R1
class R3 subclass of R2
class P1
class P2 subclass of P1
class P3 subclass of P2
class A
class B subclass of A
class A:
foo: P2->R2
class B:
foo: P?->R?
P2 invarianza sul parametro d'ingresso
P1 controvarianza sul parametro d'ingresso
P3 covarianza sul parametro d'ingresso
R2 invarianza sul parametro di ritorno
R1 controvarianza sul parametro di ritorno
R3 covarianza sul parametro di ritorno
Il C++ e' parzialmente covariante sul parametro di ritorno e 
invariante sui parametri in ingresso, infatti e' consentita una cosa 
del tipo:
class A
foo: P2->R2*
bar: P2->R2&
class B
foo: P2->R3*
bar: P2->R3&
cioe' la covarianza vale solo se i tipi di ritorno sono puntatori o 
riferimenti (10.3.5 dello standard) (ovviamente sia foo che bar 
devono essere dichiarate virtuali in A)
-- 
{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}
SunGray aka Gabriele Lana
contact me @ 
sun...@tiscalinet.it
See you space cowboy ...

Ho provato il seguente codice allegato (un piccolo file per testare cio' 
che mi hai scritto) con gcc version 3.2 20020903 (Red Hat Linux 8.0 3.2-7).
Nel main puoi trovare due versioni per la dichiarazione di b:
1. A* b = new B("BNome");
2. B* b = new B("BNome");
Con la 2 tutto funziona; con la 1 mi da errore in compilazione sulla riga:
b->foo(p)->bar();
(e non badare al fatto che lascio pezzi di memoria in giro, e' solo un 
esempio).
Secondo quanto mi hai detto dovrebbe funzionare. E' chiaro che il 
compilatore ha deciso che, nel caso 1. il ritorno e' di tipo R2 e che 
quindi il metodo bar non esiste. Mi ha passato la firma del metodo foo 
ereditato da B, e questo conferma cio' che hai detto, ma a cosa serve se 
poi mi da errore quando cerco di utilizzare in modo polimorfico il 
metodo foo? Se faccio tornare a B::foo il tipo R2 il risultato che 
ottengo e' lo stesso.
Cosa ne pensi?
Ciao
  -Ema-

   -Ema-
Ciao, Diego.
--
imho

 Ciao, Diego.
--
imho

   -Ema-


 [CUT]
Ok. Mi hai convinto.
Ciao.
  -Ema-