Newsgroup: it.comp.lang.c++
Titolo: operator->*

Prendendo spunto da un altro thread volevo fare un piccolo "sunto" degli
operatori ridefinibili in C++ perchè se gli operatori di conversione,
gli operatori di assegnamento e quelli 'algebrici' come "operator+" sono
per lo più conosciuti, alcuni operatori che sono ridefinibili (anche se
non sempre è consigliabile farlo) sono alquanto sconosiuti.
Ridefinire questi operatori di solito ha effetti strani sul linguaggio o
vengono invocati in maniera strana.
Per esempio "operator++" e "operator--", come certamente saprete,
presuppongono un 'trucco' per permettere la distinzione fra caso
prefisso e caso postfisso. E anche "operator->" viene 'chiamato' con un
piccolo trucco. Mentre "operator&" può dar luogo a comportamenti strani
(se non usato con moltissima attenzione). Per non parlare di "operator,"
(che molti neppure sanno essere ridefinibile).
Però mi sono arenato subito dopo aver fatto l'elenco degli operatori
rideinibili perchè mi sono accorto di non avere la minima idea di come
si ridefinisca "operator->*". Sospetto che valgano le stesse identiche
regole di "operator->" ma curiosamente lo standard ha un paragrafo
apposito per "operator->" nel quale non si cita "operator->*" che si
limita a elencare come ridefinibile. E non c'è nulla neppure nell'Issue
List.
Qualche idea? Dite che è il caso di aprire una Issue?
--
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

 come si ridefinisca "operator->*".
operator->* dovrebbe essere implementato in modo che restituisca un
oggetto che possa essere impiegato come una funzione, tipicamente un
oggetto di una classe provvista di operator(). 
Max

 oggetto di una classe provvista di operator().
Sei sicuro? Fammi un esempio concreto.
L'operator-> è binario ma il secondo operando non è usato per l'overload
e non è passato come parametro, viene usato a posteriori per riapplicare
operator-> sul tipo ritornato dall'operatore.
Come dichiari operator->* ? Con o senza parametro? E se il secondo
operando è un puntatore a membro dati cosa succede? Puoi farmi un
esempio di dichiarazione definizione e uso?


 per riapplicare operator-> sul tipo ritornato dall'operatore.
Non sono sicuro di seguirti qui. A me sembra che il secondo operando
venga usato per l'overload e venga passato come parametro. 
Max

 operator-> sul tipo ritornato dall'operatore.
L'operator-> e' unario e la seguente espressione e' sintatticamente
corretta:
    a->;        //Non viene interpretata come a.operator->()
non provarci con un compilatore perche il compilatore prova
a cercare un membro accessibile dopo il ->
l'operator-> funziona in due passi:
1) Invoca il metodo operator-> sull'oggetto che ha fatto l'overload di ->
operator
2) Applica -> operator sul valore ritornato dal punto 1) per accedere
all'elemento
    a destra di ->
Quindi la prima regola e' che operator-> deve tornare un puntatore ad una
classe o
ad una struttura o deve tornare un oggetto che abbia -> operator definito.
insomma in parole povere:
        a->m = 0;
lo devi vedere come:
     (a.operator->())->m = 0;
Ciao
Gaetano.
-- 
Posted from 
dell-01.mm.eutelsat.org
 [194.214.173.17] 
via Mailgate.ORG Server - 
http://www.Mailgate.ORG

 
Ops... ho risposto come se tu stessi parlando dell'operator ->*.
Max

 venga usato per l'overload e venga passato come parametro.
Parlavo di operator-> senza l'*. In questo caso il secondo operando
viene usato dal compilatore sul valore ritornato e non influisce sulla
risoluzione dell'overload.


 l'operator-> funziona in due passi:
questo lo so. E' proprio quello che ho scritto anche io!
La mia domanda verteva su operator->* non su operator->


 risoluzione dell'overload.
Ma quale secondo operando? operator-> e' unario.


 Ma quale secondo operando? operator-> e' unario.
Gaetano, non mi sto inventando le cose. Vedi la mia risposta alla tua
obiezione originale e leggiti lo standard. L'operator-> è binario. E lo
si deduce proprio dal fatto che lo standard cita esplicitamente il suo
secondo operando (se non hai lo standard, anche il reference di VC lo
lista fra gli operatori binari, ma ovviamente è lo standard che fa
testo, non la documentazione del compilatore di MS).


 come specificato in 13.3.1.2 sub 8.
Be allora Dattatri dice cavolate, lui afferma che e' unario.
E anche a me lo sembra.
Il protoripo e' :
B A::operator->()
{
}
e non vuole nessun parametro.
Del resto se scrivo
a->m = 0
 come ho gia detto, e' come se scrivessi:
(a.operator->())->m = 0;
o sbaglio?
NB:    B deve avere  -> operator definito. (tipo una classe o una struct).
Che dice lo Stroustrup sulla cardinalita' di questo operatore ?


 sua istanza. Curioso! Le implicazioni mi sfuggono. Ci dovrò pensare.
Volendo, ci si può inventare applicazioni bizzarre. Ad esempio, integrando
la definizione di ptr<T> del mio codice con
 
	delegate operator->*( const std::string& str )
	{	if( str == "pippo" )
		{
			return delegate(ptr_, &T::pippo  );
		}
		else return delegate(ptr_,&T::pluto );
	}
si possono scrivere cose come
   (p->*"pippo")(10);
 
...oppure, sempre esplorando cose insolite, ma forse, questa volta, più
sensate (mah!), ancora integrando il mio ptr<T>...
 
	template< class T1 >
	 typename ptr<T1>::delegate 
	   operator->*(  void( T1::* f )( int ) )
	  {
		T1* castptr = dynamic_cast<T1*>(ptr_);
		if( castptr )
		{	return( ptr<T1>(castptr)->*f );
		}
		throw "invalid method";
	  }
 
possiamo passare un puntatore a funzione membro appartentente a un
tipo qualsiasi gestire gli errori a run-time.
 
Max


 Che dice lo Stroustrup sulla cardinalita' di questo operatore ?
Non lo so. Ma so cosa dice lo standard. In generale lo standard non
classifica gli operatori come binari o unari o altro. Li classifica in
moltiplicativi, additivi, logici etc. Da questo punto di vista "->" è
classificato insieme a "." come "member selection operator". In 13.5.2
parla di operatori binari dicendo che possono essere implementati come
metodi con un parametro o come glubali con due. Secondo questa
concezione però si dovrebbe desumere che gli operatori "=", "()", "[]" e
"->", non sono binari. La soluzione a questo dilemma è data da 13.5 sub
9 dove si specifica che 13.5.1 e 13.5.2 sono regole generali e da 13.5.3
a 13.5.7 ci sono eccezioni. Pertanto non è più valido desumere che un
operatore è unario o binario dal fatto che segua 13.5.1 o 13.5.2,
soprattutto se l'operatore è citato in una delle clausole da 13.5.3 a
13.5.7. A questo punto l'operatore -> non viene mai classificato ne come
unario ne come binario (e lo stesso vale per quasi tutti gli altri
operatori). In 13.5.6 viene infatti detto che deve essere un membro non
statico e viene descritto il modo in cui viene interpretato, infine si
fa riferimento a 13.3 per l'overload resolution. Quindi l'unico
"indizio" resta proprio 13.3.1.2 sub 8 dove si parla esplicitamente del
"secondo operando". E se c'e' un secondo operando ...


 
Giacchè egli dice che "a unary operator can be defined by either a
nonstatic member function taking no arguments...", mi pare che
implicitamente consideri -> come un operatore unario.
Max

 implementazione.
Forse volevi dire sintatticamente e' unario e semanticamente binario.


 vedo cos'altro si potrebbe utilizzare.
Cosa intendi con proxy? Voglio dire: nel caso di un puntatore a membro
dati cosa accade dopo la chiamata ?
Ripensandoci mi pare che non sia neppure necessario ritornare un
reference all'oggetto previsto. Potrebbe tornare qualunque cosa purchè
compatibile con la sintassi dell'espressione. Persino un void, se
l'espressione lo ammette. O un long anche se il puntatore era un
puntatore a membro int. O sbaglio.
Un'altra curiosità: ti basi su cosa fa un compilatore o ti basi su
documentazione relativa allo standard o scritta da persone che fanno
parte del comitato di standardizzazione?


 implicitamente consideri -> come un operatore unario.
Probabilmente se completi la citazione vale l'opposto. Probabile che
stesse dicendo che un operatore unario può essere implementato come
membro non statico senza parametri o come globale con un parametro.
Esattamente quello che dice lo standard in 13.5.1 (e dice la stessa cosa
con rispettivamente uno e due parametri per gli operatori binari in
13.5.2). Ma lo standard dice anche che questa è la regola generale e che
ci sono eccezioni, e una delle eccezione è proprio l'operatore ->.
Pertanto lo standard stesso dice che quella frase non classifica gli
operatori.


 istanza. Curioso! Le implicazioni mi sfuggono. Ci dovrò pensare.
[snip]
Leggendo i vari messaggi di questo thread, mi e' saltata in mente un'idea 
balzana...
Utilizzando l'operatore ->* si potrebbero fare delle cose strane come
aggiungere membri al volo a un'istanza di una classe (come in certi
linguaggi tipo python o altri)
Sostanzialmente si tratta di una classe che contiene una std::map
di chiavi->valori, dove l'operator ->* restituisce un reference al valore
per una data classe. 
(DISCLAIMER: quello che segue  e' solo un "divertissement", non farei 
niente del genere in un programma "vero", praticamente tutte le regole 
di buona programmazione sono state infrante ;-)
Questo consente di scrivere cose del genere:
{
    generic_struct s;
    s->*"pippo" = "pluto";
    s->*"pluto" = 10;
    std::cout << "s.*pippo = " << s->*"pippo" << std::endl;
    std::cout << "s.*pluto = " << s->*"pluto" << std::endl;
    const std::string membro1 = "pippo";
    const std::string membro2 = s->*membro1;
    int valore = s->*membro2;
 
    std::cout << " valore = " << valore << std::endl; 
    // stampa :      valore = 10
}
Si, lo so, e' roba da far accapponare la pelle, ma e' tecnicamente possibile
Il tipo del valore associato agli pseudo-membri e' una classe che puo' 
assumere qualsiasi tipo (in maniera del tutto type-unsafe, naturalmente ;-)
Ha costruttori, operatori di assegnamento  e operatori di conversione tutti
template (aargh!) e funzionano con tutti tipi per cui sono definiti gli 
operatori << e >> per gli stream.
Mi piacerebbe poter assegnare anche funzioni a questi pseudo-membri e non 
semplicemente valori. Forse con un po' di template meta-programming ci si 
riesce anche...
Nel frattempo vi posto il codice per l'esempio di cui sopra 
(compilato con g++ 3.0)
Saluti,
Fabio
// -------------------------- 8< ------------------------
#include <string>
#include <map>
#include <sstream>
#include <iostream>
class generic_type
{
    std::string value;
public:
    generic_type() { }
    generic_type(const generic_type &gt) : value( gt.value ) { }
    ~generic_type()  { }
    generic_type & operator=(const generic_type &gt)
    {
        value = gt.value;
    }
    template <class T>
    generic_type & operator=(const T &val)
    {
        std::stringstream s;
        s << val;
        value = s.str();
        return *this;
    }
    template <class T>
    generic_type(const T &t) 
    {
        (*this) = t;
    }
 
    template <class T>
    operator T () const 
    { 
        T val;
        std::stringstream s(value);
        s >> val;
        return val;
    }        
};
//----------------------------------------------------------
std::ostream & operator << (std::ostream & os, const generic_type &gt)
{
    std::string val = gt;
    return os << val;
}
std::istream & operator >> (std::istream & is, generic_type &gt)
{
   std::string val;
   is >> val;
   gt = val;
   return is;
}
//----------------------------------------------------------
class generic_struct
{
    typedef std::map < std::string, generic_type > fields_t;
    typedef fields_t::const_iterator const_iterator ;
    typedef fields_t::iterator iterator ;
    std::map< std::string, generic_type > fields;
public:
 
    generic_type & operator->*( std::string field )
    {
       return fields[field];
    }
    std::ostream & print(std::ostream &os) const
    {
       const_iterator it;
       for(it = fields.begin(); it != fields.end(); ++it)
          os << (*it).first << " = " << (*it).second << std::endl;
    }
};
int main()
{
    generic_struct s;
    s->*"pippo" = "pluto";
    s->*"pluto" = 10;
    std::cout << "s.*pippo = " << s->*"pippo" << std::endl;
    std::cout << "s.*pluto = " << s->*"pluto" << std::endl;
    s.print( std::cout );
    const std::string membro1 = "pippo";
    const std::string membro2 = s->*membro1;
    int valore = s->*membro2;
    std::cout << "valore = " << valore << std::endl;
}
-- 
Posted from 
flopiano.mm.eutelsat.org
 [194.214.173.28] 


 
Quello che ho detto inizialmente su operator->*, ossia che questo dovrebbe
restituire qualcosa a cui si possa applicare operator(), l'ho ricavato da
TIC. In realtà, però, Eckel non dice che questo modo di definirlo sia
richiesto dallo standard, ma illustra soltanto un metodo per riprodurre la
stessa semantica della versione built-in con oggetti tipo "smart pointer".
Probabilmente lo standard non pone vincoli sul tipo che deve essere
restituito. Ho dimenticato di consultare lo Stroustrup (domani lo faccio).
Max