Newsgroup: it.comp.lang.c++
Titolo: Specializzazioni dei template

Come sapete, l'operatore dynamic_cast ha un comportamento differente
in caso di fallimento a seconda se opera su puntatori o riferimenti.
Mi sono chiesto se era possibile definire un nuovo "pseudo-operatore"
di cast che lanciasse una eccezione anche nel caso che operi su
puntatori; perciň ho scritto una cosa del genere:
template<class T,class G> T& dyn_cast(G& g) {
  return dynamic_cast<T>(g);
}
Questa dovrebbe essere la versione che opera sui riferimenti; a questo
punto ho avuto dei seri problemi a scrivere un template che accetti
solo puntatori; ho provato cosě:
template<class T,class G> T dyn_cast(G* g) {
  /* ... */
}
Ma se faccio:
void f(Base* b) {
  Derivata* d= dyn_cast<Derivata*>(b);
}
il DJGPP mi dice che č ambiguo. Come faccio a realizzare una cosa del
genere? Mi rendo conto che č un cosa abbastanza inutile, ma lo facevo
piů che altro come esercizio sull'uso dei template.
Ciao Anto
--
Antonio Cuni, 18 anni, studente
Brainbench Certification: 
Master C++: 4.6, higher than 98% of all previous test takers.
C Language: 3.32, higher than 79% of all previous test takers.
http://www.brainbench.com/transcript.jsp?pid=1301688

 piů che altro come esercizio sull'uso dei template.
Piu' che ambiguo dovrebbe dirti che il template dyn_cast vuole due parametri e
non uno. Pertanto avresti dovuto scrivere:
void f(Base* b) {
    Derivata* d= dyn_cast<Derivata*,Base>(b);
}
Perche' non provi con qualche cosa di leggermente diverso. Per esempio:
template <class Dst>
    class dyn_cast {
        Dst d;
    public:
        template <class Src>
            dyn_cast(Src&r)
                :d(dynamic_cast<Dst>(r))
            {
            }
        template <class Src>
            dyn_cast(Src*p)
                :d(dynamic_cast<Dst>(p))
            {
                // aggiungi l'eventuale throw
            }
        operator Dst() const { return d; }
    };
Questo dovrebbe funzionare, salvo che accetta anche dei Dst che non sono ne
puntatori ne reference.
--
Andrea Ferro
---------
Brainbench C++ Master. Scored higher than 97% of previous takers
Scores: Overall 4.46, Conceptual 5.0, Problem-Solving 5.0
More info 
http://www.brainbench.com/transcript.jsp?pid=2522556

     };
Questo dovrebbe produrre lo stesso problema di ambiguità, in quanto al
momento dell'instanziazione ti ritrovi con due possibili condidati come
costruttore:
  dyna_cast<B *>::dyna_cast<A *>(A *&) 
  dyna_cast<B *>::dyna_cast<A>(A *)
e non credo che il compilatore abbia il dovere di preferire il secondo.
Si potrebbe invece sfruttare la possiblità di specializzare la classe
template dyn_cast, in questo modo:
 
 template <class Dst>
     class dyn_cast { // versione standard
         Dst d;
     public:
         template <class Src>
             dyn_cast(Src& r)
                 :d(dynamic_cast<Dst>(r)) 
             {
             }
     operator Dst() const { return d; }
	 };	 
 template <class Dst>
 class dyn_cast<Dst*> // specializzazione per T*
 {
     Dst* d;
     public:
	 template <class Src>
             dyn_cast(Src* p)
                 :d(dynamic_cast<Dst*>(p))
             {
                 // aggiungi l'eventuale throw
             }
     	operator Dst* () const { return d; }
     };
 
 int func( A* p)
 {
     B& b = dyn_cast<B&>( *p);
 }
 
 int func( A* p)
 {
     B* b = dyn_cast<B*>( p );
 }
 
Max

      };
Credo sia la cosa migliore.


mezzo.
invece dovebbe essere possibile; sullo Stroustrup, paragrafo 13.3.1,
c'è un esempio di funzione implicit_cast<>() che usa proprio la
tecnica del tipo di ritorno specificato e del tipo paramtro dedotto.
[cut sul resto]
Ma non vi pare strano che non ci sia un modo per fare una funzione
sullo stile di quelle predefinite? (ok, dynamic_cast è un operatore,
non una funzione). Si può ricorrere alla classe, ma mi sembra un po'
macchinoso. La filosofia del C++ non era quella di uniformare il
comportamento del linguaggio con quello delle funzioni predefinite e
di libreria? Per intenderci, non come in Pascal dove le procedure
write e read hanno subito l'overloading ma il programmatore non può
usarlo.
Altra domanda, che poi è il motivo per cui non capisco bene cosa
succede; se io faccio:
template<class T> void f(T) { }
template<class T> void f(T*) { }
Non si presenta il problema dell'ambiguità.
Se invece faccio:
template<class T> void f(T&) { } // oppure anche const T&
template<class T> void f(T*) { }
è ambiguo. Perchè?


template dyn_cast, in questo modo:
così funziona, grazie.


è ambiguo. Perchè?
A me sembra corretto indicare l'ambiguita'. Se scrivi
   int x;
   f(x)
stai passando per valore o per reference ?
Se poi usi un const reference allora anche f(3) secondo
me e' ambiguo. Questo anche senza tirare in ballo i template.
Nel caso dei puntatori la cosa e' completamente diversa...
un determinato tipo e' un puntatore oppure non lo e'.
Il fatto di poter scrivere specializzazioni di template per
puntatori generici mi sembra logicamente solida; in C++ non
avvengono conversioni implicite fra puntatori e oggetti
puntati... e' assolutamente chiaro se sto indicando un
puntatore oppure l'oggetto puntato (e' possibile costruire
classi "handle" che si comportano come puntatori ma che
prevedono una conversione implicita al dato puntato...
questo e' pero' un discorso diverso).
Un reference non e' una specie di puntatore con conversione
implicita al dato puntato; e' semplicemente un alias per il
compilatore. I reference "non esistono" in quanto tali, in
C++ e' pero' possibile passare valori o restituire risultati
per reference (mentre in C il passaggio e' sempre per valore
e quindi si devono usare puntatori e dereferencing esplicito).
Personalmente trovo strane e "asimmetriche" cose come il
poter inserire un reference all'interno di una classe
(mentre ad esempio dichiarare array di reference e' vietato)
o fare il binding di reference a valori temporanei che
dureranno quanto il reference (mentre questo e' falso per
il valore di ritorno da funzioni visto che in questo caso
il reference punta ad un valore temporaneo che muore prima
del reference).
Andrea
--
Andrea Griffini, Programmer
http://www.netservers.com/~agriff

 istanziare. Non esiste una "partial specialization" per le funzioni!
Ora capisco. Ero convinto che esistesse la specializzazione parziale anche
per le funzioni template. Da qui la mia considerazione gratuita sul gcc.
Max

 è ambiguo. Perchè?
Il criterio per risolvere l'ambiguita' e' scegliere la versione piu'
specializzata. Date due specializzazioni A e B A e' piu' specializzata
di B se esiste un tipo T che combacia con B ma non con A, ma non esiste
alcun tipo U che combacia con A ma non con B, ovvero se A puo'
combaciare con un sottoinsieme proprio dei tipi con cui puo' combaciare
B.
Nel primo esempio tutti i tipi combaciano con la prima funzione, mentre
con la seconda combaciano solo i puntatori; nel secondo esempio con la
prima combaciano solo i reference, mentre con la seconda solo i
puntatori. Di qui l'ambiguita'.
Ciao,
Nicola

 istanziare. Non esiste una "partial specialization" per le funzioni!
Attenzione che anche in assenza di specializzazione parziale, non tutte
le specializzazioni sono istanziazioni! Ad es.:
template <typename T> f(T t);
template <typename T> f(T * t);
Ciao,
Nicola

stai passando per valore o per reference ?
in questo caso non si tratta di scegliere tra passaggio per valore o
reference, ma tra reference e puntatore.
Questa cosa potrebbe anche non sorprendermi più di tanto: il
compilatore potrebbe dire: "anche se tu mi passi un int*, sono
autorizzato a chiamare la prima versione e passargli un int*&". Quello
che mi stupisce è che se nel primo template il passaggio è per valore
l'ambiguità non esiste; eppure potrebbe anche considerare di passare
un int* per valore (ovvero fare la copia del puntatore, che è quello
che succede nel secondo caso).


per le funzioni template. Da qui la mia considerazione gratuita sul gcc.
a questo punto la domanda è d'obbligo: perché non esiste la partial
specialization per le funzioni? Abbiamo visto che c'è almeno un caso
in cui sarebbe utile (questo); quali sarebbero le controindicazioni?


 in cui sarebbe utile (questo); quali sarebbero le controindicazioni?
Perche' per le funzioni libere la specializzazione parziale corrisponde
esattamente ad un overload. Nota che il problema di distinguere una
specializzazione su reference da una su puntatore e' indipendente dal
concetto di specializzazione parziale. Negli altri casi il problema non
e' tanto di poter scrivere la specializzazione, quanto il fatto che puo'
risultare impossibile risolverla tramite il riconoscimento implicito:
prova ad indicare esplicitamente i parametri dei tuoi template di
esperimento.
Ciao,
Nicola

che è quello che succede nel secondo caso).
Buona caccia... io torno in letargo a cercare di digerire la
colomba :P


 è ambiguo. Perchè?
Per lo standard hai un overload fra due templated functions. E' in corso un
dibattito per modificare 14.5.5.2 dato che in questo specifico caso non e'
chiaro in quali casi la cosa debba considerarsi ambigua e in quali no. Di sicuro
se chiami la funzione passando un puntatore che non e' un lvalue e la versione
reference non e' costante, allora non c'e' ambiguità e la seconda vale. Se
invece la reference e' costante o se il puntatore  e' un lvalue esiste
certamente una ambiguità in termini di overload resolution. Questa ambiguità
dovrebbe essere risolta proprio dal partial ordering descritto in 14.5.5.2. A
seconda di come si interpreta quel paragrafo ci sono due casi:
1) dato un "synthetized unique type" chimiamolo "SUT" l'esecuzione
dell'algoritmo di template argument deduction sul template con la reference per
un argomento "SUT*" ha successo (il che significa che "SUT*" va interpretato
come un lvalue). In questo caso non esiste ambiguità perche' la seconda funzione
e' piu' specifica della prima e viene chiamata questa.
2) dato un "synthetized unique type" chimiamolo "SUT" l'esecuzione
dell'algoritmo di template argument deduction sul template con la reference per
un argomento "SUT*" fallisce (il che significa che "SUT*" va interpretato come
un rvalue). In questo caso, se la chiamata vera e propria avviene con un lvalue
abbiamo una ambiguità mentre se avviene con un rvalue viene chiamata la seconda.
Nota che, a meno che nel ritoccare 14.5.5.2 non lo cambino anche nella sostanza,
se la reference e' costante hai sempre e comunque una ambiguità, cosa che sta
creando qualche problema proprio in questi ultimi due mesi a Scott Meyers (vedi
recente discussione su comp.std.c++ fra Scott Meyers, James Kuyper Jr, Jeff
Peil, il sottoscritto e altri).
Nota: Dalle ricerche di Scott pare che i compilatori piu' "conformi" sotto
questo punto di vista siano MWCW 6.1 e g++ 2.95.2 e che g++ 2.97 sia meno
"conforme" allo standard del predecessore (salvo che, come detto sopra, lo
standard stesso e' ambiguo e pertanto dire che si e' piu' o meno conformi e'
abbastanza nonsense).


 wrote in message
news:3ADC00A9...@divalsim.it...

Scritta cosi' non e' una specializzazione ma un overload!


 del reference).
Questa non l'ho capita. Qualunque oggetto sia legalmente legabile ad una
reference e' garantito che durerà almeno quanto la reference. Non puoi avere un
reference ad un valore temporaneo che muore prima del reference stesso.


 che succede nel secondo caso).
Infatti dal punto di vista dell'overload resolution la cosa e' sempre ambigua.
Ma puo' non esserlo se tiri in ballo il partial ordering fra templated function.
Nel caso in cui il primo template invece che prendere T& prenda T e' ben
definito che il seocondo template e' piu' specifico secondo il partial ordering
e pertanto il primo template non va preso in considerazione se e' possibile
usare il secondo. Ergo dato che in fase di overload resolution il primo template
non esiste, l'ambiguità non esiste.


 colomba :P
La risposta e' che non puo' perche' nel primo caso e' chiaro che il secondo
template e' piu' specifico e pertanto il primo template non va applicato.


 wrote in message
news:3ADBFD31...@divalsim.it...

Non e' cosi' semplice. Per esempio dire in generale che un puntatore ad intero
non combacia con un reference non e' vero. Dipende dal reference e dipende dal
"l/r-valueness" del puntatore. Esempio:
int i, *p;
in questo caso abbiamo che "p" 'combacia' con "int*&" mentre "&i" no.


 "l/r-valueness" del puntatore.
Non l'ho mai detto, infatti. Pero' perche' ci sia ambiguita' e'
sufficiente che esistano due tipi dei quali uno combacia con una
specializzazione e l'altro con l'altra. In particolare, come suggerisci,
un puntatore che sia rvalue combacia solo con la specializzazione per
puntatore mentre qualsiasi variabile che non sia di tipo puntatore
combacia solo con la specializzazione per reference.
Ciao,
Nicola

 rvalue e pertanto non puo' essere mai chiamato il secondo template.
Gia'. BCC ha un sacco di problemi con il binding di valori temporanei a
reference non costanti. La sensazione e' che ci sia un errore logico nel
parser e che non sia mai stato corretto, e che solo a posteriori, e
quindi solo in alcuni casi specifici, gli e' stata appiccicata sopra
l'emissione di warning.
Personamente penso che l'accettare di legare temporanei a reference non
costanti possa introdurre bachi abbastanza sottili, specie se ci si basa
sul fatto che non dovrebbe essere legale. Secondo me il warning dovrebbe
essere trasformato in un errore bloccante.
Ciao,
Nicola

esattamente ad un overload. 
intendi questo?:
template<class T> void f(T) { }
void f(int) { }
f("ciao"); // template
f(1); // non-template
ho capito bene? Ma in questo caso come si comporta il compilatore in
caso di conversioni implicite?
f('a'); // f<char>(char) o f(int) ?
Seguendo a rigore quello che hai scritto in un altro messaggio di
questo thread dovrebbe scegliere quella template (f(char) č piů
specializzata di f(int)); č giusto o ci sono delle eccezioni alla
regola? E se fosse necessario che nel caso sopra richiami la versione
non-template? L'unica soluzione che mi viene in mente č fare un
wrapper inline:
inline void f(char c) { f(static_cast<int>(c)); }
oppure c'č un altro modo piů elegante?
Ciao Anto


 regola?
La regola citata non riguarda le funzioni non templated ma solo le funzioni
templated. Ovvero specifica un partial ordering fra funzioni templated.



Tempo fa ebbi una discussione riguardo questo punto sul forum
della Borland.
In effetti tutto quello che lo Standard richiede è l'emissione di un
diagnostico (la cui natura è implementation defined).
Tuttavia mi lascia piuttosto perplesso il fatto che mentre un error
blocca la compilazione un warning no.
Se consideriamo poi la selva di warning che si ottengono a volte....
Saluti   Manlio Perillo

 wrote in message
news:mokD6.34516$r67.7...@news2.tin.it...

Il fatto che un error blocca la compilazione e un warning no ... fa parte della
definizione di error e di warning. In generale i messaggi diagnostici si
chiamano error se bloccano la compilazione e warning se non la bloccano!
Nota che quando uno standard richiede che venga emesso un diagnostico, o lascia
libera l'implementazione di definire la natura del diagnostico o richiede che
sia un warning. Uno standard non richiede mai che un diagnostico sia un error.
Questo per motivi di liberta' implementativa: altrimenti non sarebbe possibile
avere un compilatore unico che compila piu' linguaggi. Quindi se lo standard
dice che e' richiesto un diagnostico senza specificare che deve essere un
warning una implementazione che supporta solo il C++ e nessun altro linguaggio
(e nessuna estensione "della casa") dovrebbe sempre dare un error.


Nel C++ ce n'e' parecchia!
quindi risolviamo il quesito con un abracadabra ;-)
la cosa che mi stupiva č che ciň che volevo fare era possibile
definendo una classe ma non una funzione; cmq la comprensione di
questo thread mi sta un po' sfuggendo di mano, non credo di riuscire a
comprendere fino in fondo tutto le cose, peraltro molto interessanti,
che dite...
Ciao Anto
PS Ne approffitto per salutarvi, visto che parto; ci risentiamo
mercoledě


 (e nessuna estensione "della casa") dovrebbe sempre dare un error.
Quest'ultima non mi pare una ragione valida. Il comportamento del
compilatore multi-linguaggio e' determinato dal tipo del suo input ed
l'implementatore e' libero di vincolare il modo per determinare tale
tipo, ad esempio utilizzando l'estensione del file. Nessuno standard del
C++ puo' richiedere che io emetta diagnostici, se sto compilando codice
Fortran.
Pero' e' vero che il tipo e la varieta' di diagnostica e' uno degli
elementi su cui si gioca la concorrenzialita' delle diverse
implementazioni. Inoltre lasciare liberta' nella scelta di quali errori
considerare bloccanti e' indispensabile per lasciare agli implementatori
la possibilita' di fornire compatibilita' all'indietro.
Ciao,
Nicola

 la possibilita' di fornire compatibilita' all'indietro.
Ed estendibilitą. Appunto.


 Server Pages).
Sei comunque in "undefined land", quindi lo standard se ne lava le mani.
Ciao,
Nicola

 wrote in message
news:3ADED09B...@divalsim.it...

Non del tutto pero'. Se ne lava le mani senza proibirlo. Appunto evitando di
richiedere esplicitamente degli errori ma richiedendo al massimo dei diagnostici
(che quindi possono essere non bloccanti).


templated. Ovvero specifica un partial ordering fra funzioni templated.
grazie anche per questa (necessaria) precisazione.
