Newsgroup: it.comp.lang.c++
Titolo: A proposito di implicito ed esplicito...

Dato il semplice prog:
template< class T > class base{
public :
  void a() {}
};
template< class T > class derivata : public base< T >{
public :
  void b() { a() ; }
};
int main() {
 derivata< int > d ;
  d.b() ;
  return( 0 ) ;
}
Sebbrerebbe tutto corretto, e chiamando d.b() si richiama
derivata<int>::b(), la quale chiama a sua volta base<int>::a().
E invece no, ed il codice in questione darebbe un errore di compilazione su
di un compilatore realmente ANSI/ISO compliant.
Secondo lo standard, infatti, alcuni nomi all'interno di un template
vengono detti "dipendenti", cioe' dipendono dai parametri template. Ad
esempio,
sono dipendenti i nomi dei parametri, oppure, se un parametro e' T, B<T> e
simili sono a loro volta dipendenti (par. 14.6.2).
Questi nomi dipendenti vengono presi in considerazione *solo* al momento
dell'istanziazione del template, non al momento della dichiarazione.
I nomi "non dipendenti", invece, vengono considerati al momento della
dichiarazione del template, ma non al momento dell'istanziazione.
Se abbiamo un template come il seguente:
template< class T > class A{
  void f1() { f2(); }
};
il nome "f2" e' non dipendente dal parametro, quindi viene preso in
considerazione al momento della dichiarazione: se nello scope di
dichiarazione di A non esiste una funzione f2, avremo un errore.
Arriviamo ora al punto cruciale: all'interno di derivata, a() e' un nome
*non* dipendente.
I nomi non dipendenti vengono cercati al momento della dichiarazione, ma lo
scope delle classi base *dipendenti* (come la nostra base) non viene
considerato sino all'istanziazione (14.6.2.4 dello standard).
Il motivo e' relativamente semplice: base< int > potrebbe essere
completamente diversa, e potrebbe non avere una funzione a().
Percio', il codice dato e' errato, ed dovrebbe produrre un messaggio di
errore del tipo "nome a() sconosciuto" al momento del parsing della
dichiarazione di derivata.
Ancora peggio: se ci fosse stata una funzione globale a(), il codice di cui
sopra sarebbe stato legale, ma derivata<int>::b() avrebbe chiamato la a()
globale, non base<int>::a().
Per evitare il problema, la tecnica migliore e', IMHO, l'uso esplicito di
"this":
template< class T > class derivata : public base< T >{
public :
  void b() { this->a() ; }
};
In quanto this e' dipendente dal parametro, e l'espressione this->a()
diventa una espressione dipendente, con il corretto "significante" alla
Martelli :)
Esempi del genere fanno riflettere: Il codice basato su template ha molti
dettagli "impliciti" nell'apparente semplicita' di un template <class T>, e
il codice scorretto di cui sopra sembra troppo "familiare", e introduce una
disparita' di trattamento tra classi template e non.
Riprendendo il discorso di alex sul mainstream del c++, nel 3d sul new, i
cosiddetti guru inneggiano alla flessibilita', ma stranamente pochi di loro
fanno notare questo tipo di problemi, derivanti proprio dalla
flessibilita'. "Stranamente" proprio perche' l'auto-critica e' sempre stato
un elemento caratterizzante della cultura del C e del C++. "Stranamente"
(di nuovo) con i template questa tradizione di chiarezza sui potenziali
problemi sembra essersi interrotta.
Certo la soluzione, in questo caso, diventa abbastanza automatica: usare in
modo sistematico this-> per accedere "esplicitamente" a dati e  funzioni
membro dei template.
Quindi non sempre l'esplicito e' un dettaglio stilistico, ma puo' essere
l'unico metodo per prevenire errori "strani". Con questo non voglio dire
che debba farsi tutto esplicitamente, sia chiaro, ma che per usare
conversioni, chiamate, ecc. implicite bisogna sapere bene come il
compilatore interpreta cio' che si sta scrivendo, e cosa in realta' si
vuole ottenere, alla faccia della flessibilita' sbandierata da molti.

Non mi hai convinto; non ho a disposizione lo standard, ma mi pare
arbitrario considerare il tuo a() come non dipendente, poiche'
trattandosi di una classe dotata di superclasse a puo' combaciare tanto
con ::a() (certamente indipendente) che con Base::a() (forse
dipendente). Perche' non posti il tuo problema su comp.std.c++ e ci fai
sapere la risposta?
Ciao,
Nicola

 wrote in message
news:396C4F8B...@divalsim.it...

Secondo me Sandro ha ragione, perche` non c'e` nessuna
garanzia che Base<T> abbia un membro a() per qualsiasi
T.  Non lasciarti ingannare dall'apparenza -- *sembra* che
debba averla, perche` subito prima abbiamo visto la
dichiarazione del template Base<T>... ma *nessuno ci
vieta* immediatamente dopo di fare una specializzazione
Base<int>, che NON ha a(), e poi istanziare derivata<int>.
Il concetto di nome dipendente o meno e` stato introdotto
proprio per evitare qualsivoglia ambiguita` in questa
situazione, e permettere la, diciamo, pre-compilazione
parziale dei template (controllo sintattico, come minimo)
in modo univoco e non dipendente dall'interpretazione di
uno o dell'altro specifico compilatore.
Quello su cui sono in totale disaccordo con Sandro e` che
questa particolarita` sia in alcun modo un'eccezione alla
filosofia generale del C++.  L'importante, in C++ come in
C, e` sempre stato che un ipotetico programmatore perfetto,
con 100% di padronanza di tutte le regole, possa fare tutto
quel che gli serve "alla perfezione".  Che questo possa rendere
la vita un inferno per il disgraziato che, con padronanza che
e` invece limitata al solo 99.44%, incappa nelle anomalie
piu` strampalate ed astruse (che puo` capire solo giungendo
al teoretico "100%" di cui sopra), non ha mai avuto la benche`
minima importanza.
Ragazzi, e` capitato a B. Stroustrup in pieno newsgroup
comp.lang.c++.moderated (relativamente al fatto che un
std::vector debba allocare memoria contigua: era certo che
lo standard lo specificasse, salta fuori che lo standard non
lo specificava).  Quando succede a BS, magari puo` anche
far cambiare lo standard (come ha fatto in questo caso, con
un defect report).  Quando succede a noi, e` difficile che
cambino lo standard per tenerci buoni.  Ma il punto e` che
neppure BS padroneggia il 100% del linguaggio C++.  Non
e` possibile: il linguaggio in questione e` *TROPPO*.  Cio`
e` vero anche per altre tecnologie, incidentalmente, benche`
non per gli altri linguaggi piu` diffusi.
Alex

 wrote in message 8khpc9
| Secondo me Sandro ha ragione,
[...]
:) Grazie!
| Il concetto di nome dipendente o meno e` stato introdotto
| proprio per evitare qualsivoglia ambiguita` in questa
| situazione, e permettere la, diciamo, pre-compilazione
| parziale dei template (controllo sintattico, come minimo)
| in modo univoco e non dipendente dall'interpretazione di
| uno o dell'altro specifico compilatore.
Gia'. il bello e' che codice come quello sopra (senza la ::a()) compila su
alcuni, e su altri no, come fosse implementation defined, anche se secondo
lo standard e' errato...
| Quello su cui sono in totale disaccordo con Sandro
[...]
E questo era scontato... :) pero'...
1:
| L'importante, in C++ come in C, e` sempre stato che un ipotetico
| programmatore perfetto,con 100% di padronanza di tutte le regole,
| possa fare tutto quel che gli serve "alla perfezione".
| Che questo possa rendere la vita un inferno per il disgraziato che,
| con padronanza che e` invece limitata al solo 99.44%, incappa nelle
| anomalie piu` strampalate ed astruse (che puo` capire solo
| giungendo al teoretico "100%" di cui sopra), non ha mai avuto la
| benche`minima importanza.
[...]
2:
| Ma il punto e` che
| neppure BS padroneggia il 100% del linguaggio C++.  Non
| e` possibile: il linguaggio in questione e` *TROPPO*.  Cio`
| e` vero anche per altre tecnologie, incidentalmente, benche`
| non per gli altri linguaggi piu` diffusi.
Di sicuro 1 e' un po' in contrasto con 2, no?-)
Inoltre il mio esempio, come sembrano confermare le tue parole su BS,
sembra non essere un caso sporadico in cui ci si trova spiazzati dallo
standard su come e cosa aspettarsi da una feature del linguaggio...
E allora forse concordiamo su questo: se nemmeno il suo ideatore lo
padroneggia al 100%, il voler rendere tutto "implicito" per forza, anche
laddove non si abbiano cognizioni precise a riguardo del comportamento del
codice in questione, in nome di una presunta *flessibilita'*, e' un metodo
di programmazione che si rivela potenziale fonte di bug non solo per chi
scrive le librerie e i compilatori, ma anche per noi poveri cristi che
dobbiamo poi usarli.
Una libreria che implementi codice come quello sopra (cosa che non mi
sembra molto improbabile), di sicuro non rende la vita facile a chi poi la
utilizza, quando si trova di fronte ad un link che fallisce, o una funzione
chiamata in modo "random", o altro...

 chiamata in modo "random", o altro...
"Rendere la vita facile" *NON* e` la prima direttiva del C++ (neppure
di nessun altro dei linguaggi suelencati, tra l'altro: se e` quello
che vuoi, guarda se mai Smalltalk, o Python).
In concreto, i _reali_ vantaggi del C++ rispetto ad altri linguaggi
sono, sostanzialmente (visto che, a differenza delle Ferrari, non ti
aiuta a rimorchiare): le prestazioni; le prestazioni; e le prestazioni.
(Cosi` come, rimorchi a parte, e` la velocita` il vantaggio Ferrari).
Se le prestazioni sono il tuo pane (e salame), usa C++ al meglio (e
ringrazia che c'e`: Assembler, C, e altre alternative di bassissimo
livello, sarebbero peggio).  Se usi C++ per codice che potrebbe
sopportare (diciamo) un 20% di rallentamento, e che quindi potresti
scrivere in altri linguaggi, stai commettendo un errore.
Alex

 wrote in message
| E cosi` le implementazioni che tu usi non implementano il
| 100% dello standard?  Ma che sorpresa, ma guarda te...!
OOOOHHHH, ma davvero dici?-)
| No.  Sia 1 sia 2 sono dati di fatto, quindi come possono essere
| in contrasto fra loro?
la prima e' una *tua* affermazione, il secondo un dato di fatto...
o citavi?-)
| E chi hai soldi per permettersi una Ferrari (un'auto studiata per un
| pilota perfetto: io, che sulle mie capacita` di guida sono modesto,
| preferisco la mia modesta Honda!-), se la piglia (e rimorchia --
| non roulotte, per intenderci).
gia', ma se la ferrari non avesse l'abs, l'airbag, etc, etc, tutti
prenderebbero l'honda no? nel senso, se usiamo il C++, ci sara' un motivo?
| Non e` sporadico.  Il C e il C++ sono *pieni* di trabocchetti
| riposti e sottilissimi.
[...]
| Non e` questione di metodo, ma di linguaggio.
gia', ma come per la ferrari, perche' non allacciare la cintura di
sicurezza, visto che c'e', anche se andiamo a 300 km/h? Esplicitare un
this->, o una f() template su una struct, non hanno quell'overhead che
dici, visto che, per la struct, il costruttore gia' l'hai esplicitato...
| "Rendere la vita facile" *NON* e` la prima direttiva del C++ (neppure
| di nessun altro dei linguaggi suelencati, tra l'altro: se e` quello
| che vuoi, guarda se mai Smalltalk, o Python).
L'ho fatto, stanno bene dove stanno ;-)
| In concreto, i _reali_ vantaggi del C++ rispetto ad altri linguaggi
| sono, sostanzialmente (visto che, a differenza delle Ferrari, non ti
| aiuta a rimorchiare): le prestazioni; le prestazioni; e le prestazioni.
| (Cosi` come, rimorchi a parte, e` la velocita` il vantaggio Ferrari).
| Se le prestazioni sono il tuo pane (e salame), usa C++ al meglio (e
| ringrazia che c'e`
Stroustroup sempre sia lodato!
| Se usi C++ per codice che potrebbe
| sopportare (diciamo) un 20% di rallentamento, e che quindi potresti
| scrivere in altri linguaggi, stai commettendo un errore.
Cosi' pero' dovremmo riscrivere tutto il kernel di NT in java!
Come, c'e' il C#?-)

 Come, c'e' il C#?-)
Dubito seriamente che il kernel di NT, nel suo complesso, possa
supportare un ulteriore 20% di rallentamento.  La gestione
automatica della memoria, che e` un toccasana per il 90+% dei
programmi, e` poi un problema per alcuni specifici casi (hard
real-time, ecc) che possono comprendere almeno alcuni degli
strati che costituiscono un kernel.  Con tutto cio`, un "JavaOs"
e` sicuramente concepibile (ed e` piu` volte stato proposto, e
senza dubbio sperimentalmente realizzato a un qualche livello),
anche se sospetto che per essere davvero efficiente potra`
richiedere hardware particolare.
Alex

 (Cosi` come, rimorchi a parte, e` la velocita` il vantaggio Ferrari).
A me pare che la varieta' di costrutti disponibili lo renda, tra i
linguaggi maggiormente diffusi, uno dei piu' espressivi e considero
questo un vantaggio molto spesso superiore alle prestazioni.
Ciao,
Nicola

 wrote in message
pre: prendi sempre tutto molto alla lettera, vero?-)
| > this->, o una f() template su una struct, non hanno quell'overhead che
| > dici, visto che, per la struct, il costruttore gia' l'hai
esplicitato...
|
| Se il compilatore interpreta X come this->X, per qualsiasi X, allora
| scrivere esplicitamente this->X non ha alcun overhead di runtime.
| Analogamente, se X e` un numero, scrivere X+0 non ha alcun overhead
| di runtime.  E neppure 0+X+0+0+0.  Proponi dunque di usare questa
| forma?  E perche` fermarsi qui?
ho piu' volte scritto "ora non pensare che voglia esplicitare anche la
mammina", intendendo quello che stai dicendo ora...
|     "fai la cosa piu` semplice che possa funzionare".
| Se una cosa non funziona, non e` "la piu` semplice che possa
| funzionare", quindi, fanne un'altra: nota come dal principio
| consegua dunque, come caso particolare, il dettame di non usare
| in un template un disadorno X dove intendi this->X ereditato
| da una classe-base:-).
concordo, era appunto questo il discorso. Volevo solo far notare che, per
il tizio col 99,4% di know-how, e non i semi-dei par tuo :), viene naturale
la prima forma.
[...]
| Ma esplicitare il tipo degli argomenti di una funzione template,
| che il compilatore sa dedurre, viola il principio, non perche`
| non funzioni, ma perche` NON e` la cosa piu` semplice.
mai detto questo: il mio era da intendersi come un invito a riflettere su
cosa si puo' e cosa non si puo' rendere implicito...
[...]
| > Cosi' pero' dovremmo riscrivere tutto il kernel di NT in java!
| > Come, c'e' il C#?-)
[cut su prestazioni nel kernel]
ma dai, era un joke!-)

 cosa si puo' e cosa non si puo' rendere implicito...
Non mancare inoltre di riflettere su COME puoi esplicitare le cose.
Se usi X piu` volte, scrivere ogni volta "this->X" e` ottimale?  O non
ti converrebbe UN singolo esplicito "using Base::X" per poter cosi`
tornare all'uso implicito di X altrove?  (Attenzione, piccolo quiz di
C++: perche` *NON* sarebbe equivalente scrivere ad es:
    Base::X();
al punto di uso, invece che this->X() o "using Base::X" prima e
    X();
al punto di uso...?  Hint: una classe svolge MOLTEPLICI ruoli).
Alex

 brevis", e l'"ars" del C++ e` fra le piu` lunghe che esistano.
Che con Java sia piu' difficile "incartarsi" che in C++ ne sono
convinto. Mi pare pero' che una parte della maggior produttivita' si
perda per la maggior prolissita' cui Java porta, per l'assenza di alcuni
costrutti e meccanismi del C++: templates, distruttori, istanziazione
automatica, eredita' multipla.
Che ne pensi? E' vero per contro che Java, come spesso succede per i
linguaggi interpretati (o giu' di li...), mette a disposizione una
libreria standard molto ricca.
Ciao,
Nicola

 libreria standard molto ricca.
Si`, ma nel C++ da cui facevo il porting avevo una libreria
(non-standard) ancora piu` ricca (ai nostri specifici scopi),
le nostre "librerie di base", evolute negli anni per fornire
in modo portatile alle nostre applicazioni tutte le varie
funzionalita` di sistema ad esse utili (nonche` progettate e
fatte in buona parte da me, e quindi non lontane dalle mie
personali preferenze:-).  Diciamo che questa parte e` stata
una patta!-)  [Ed e` gia` un bel complimento alle librerie
standard di Java...].
L'osservazione (empirica) del 20% o piu` di guadagno di
produttivita` e` il "netto" fra i vantaggi e gli svantaggi.
Purtroppo, i miei appunti dell'epoca non riportano abbastanza
dettagli quantitativi da permettermi di discernere oggi fra
i due effetti con precisione.  Sospetto (ma non lo posso
asserire con certezza) che il tempo di progetto e stesura
del codice fu sostanzialmente lo stesso che in C++, e il
grosso del guadagno venne dal minor debugging necessario.
(Immagino, naturalmente, che, se un principiante di Java
puo` progettare e scrivere il codice grosso modo nello
stesso tempo che userebbe nel C++ che usa da anni, quando
l'esperienza nei due linguaggi diviene equivalente l'uso
di Java mostri una misura di vantaggio).
Alex