Newsgroup: it.comp.lang.c++
Titolo: problema con passaggio per indirizzo e operatori

salve a tutto il ng,
ho un problema abbastanza ostico a cui non so trovare soluzione. Ho scritto 
questa semplice classe per gestire dei vettori in 3 dimensioni
(a cui ho tolto un po' di roba non utile alla comprensione del problema)
class Vector {
public:
        Vector();
        Vector(float x, float y, float z);
 
        friend inline Vector operator+(Vector &a, Vector &b);
        friend inline Vector operator-(Vector &a, Vector &b);
        friend inline Vector operator*(Vector &a, float coeff);
        friend inline Vector operator*(float coeff, Vector &a);
        friend inline Vector operator/(Vector &a, float coeff);
 
protected:
        float m_x,m_y,m_z;
};
inline Vector operator+(Vector &a, Vector &b) {
        return Vector(a.m_x+b.m_x,a.m_y+b.m_y,a.m_z+b.m_z);
}
e altre analoghe funzioni per gli altri operatori.
Il problema sorge quando devo chiamare questa funzione
void multLookAt(Vector &eye, Vector &at, Vector &up);
se la chiamo cosi'
// a e b sono Vector *, ed f->center e' definita come
// Vector &center() { return m_center; } 
// con m_center Vector della classe Frame
Vector prova  = *a - f->center();
Vector prova2 = *b - f->center();
multLookAt ( prova , prova2, up);
funziona egregiamente. Il problema sorge quando provo a togliere di mezzo 
prova e prova2 scrivendo
multLookAt ( (*a - f->center()),(*b - f->center()), up);
e l'errore (abbastanza criptico, gia' solo l'interpretazione meriterebbe un 
post) che ottengo e'
molview.cpp:320: could not convert `operator-((+f->Frame::center()))'
to `Vector &'
vectop.h:4: in passing argument 1 of `multLookAt (Vector &, Vector &,
Vector &)'
make: *** [molview.o] Error 1
lavoro sotto linux e gcc 2.96
grazie anticipatamente

 ha scritto nel messaggio
news:9rkmql$5kt$1...@news.ngi.it...


 megaloborg
nada... provato. stesso errore.

 }
A me pare che ci sia un errore nell'implementazione di operator +: forse
mi sfugge qualcosa, ma quando si fa l'overloading di un operatore come
il +, è sufficiente passare alla funzione un solo operatore, questo
perchè la notazione "a + b" in pratica è equivalente a "a.operator+(b)".
Tu invece hai dichiarato l'operatore passando due parametri, per cui per
richiamarlo dovresti fare "a.operator+(b, c)" o in alternativa "a + (b,
c)", restituendoti tra l'altro la somma dei vettori b e c, che non credo
sia il motivo per cui hai sovraccaricato il +.
Può darsi che l'errore che ti dà il compilatore sia da ricondurre a
questo.
Ciao,
Cecco

multLookAt ( (*a - f->center()),(*b - f->center()), up);
Devi dichiarare multLookAt in questo modo:
void multLookAt(const Vector &eye, const Vector &at, const Vector &up)
oppure:
void multLookAt(Vector eye, Vector at, Vector up)
Il problema č dovuto al fatto che non puoi passare per reference
degli oggetti temporanei (a meno che non li dichiari const).
-- 
Ciao, Marvin

 megaloborg
E che e' Lisp ?
PS. Quota correttamente.
Ciao
Gaetano.
-- 
Posted from 
nts-03.mm.eutelsat.org
 [194.214.173.15] 
via Mailgate.ORG Server - 
http://www.Mailgate.ORG

 perchè la notazione "a + b" in pratica è equivalente a
"a.operator+(b)".
No. La notazione "a+b" è equivalente a "a.operator+(b)" OPPURE a
"operator+(a,b)" a seconda di come hai definito l'operatore. Se
l'operatore è un membro della classe hai ragione tu. Ma nel caso
specifico l'operatore è una funzione esterna alla classe (nella classe è
dichiarata friend solo perchè accede ai membri privati). Che la scelta
sia discutibile può anche essere. Ma scorretta non lo è di certo.
--
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

 void multLookAt(Vector &eye, Vector &at, Vector &up);
Non sono sicuro di aver capito cosa serve. Se č un cross product di eye
e at con risultato in up ridefiniscila cosě:
void multLookAt(const Vector &eye, const Vector &at, Vector &up);
Altrimenti spiegati.


 sia il motivo per cui hai sovraccaricato il +.
Come vedi munehiro a dichiarato l'operatore+ come funzione friend.
Non e' un membro della classe. Si puo implementare l'operatore + in
entrambi i modi, ma e' preferibile il come funzione friend.
Vengo e mi spiego.
Se tu avessi supponi:
class Pippo {
    .....
    public:
        Pippo(int a) { .... }
};
Con l'operatore + definito come membro puoi fare la seguente
cosa:
Pippo   mioPippo;
Pippo   tuoPippo;
int        mioInt;
tuoPippo  mioPippo + mioInt;
// qui il compilatore crea un Pippo da mioInt
// e applica tuoPippo  mioPippo.operator+(Pippo(mioInt));
ma se fai:
tuoPippo  mioInt + mioPippo;
il compilatore non puo fare quello che faceva prima.
Se invece trova una free function (nota che ho messo
i const altrimenti non puoi passare un valore temporaneo
come non const reference)
Vector operator+(const Vector &a, const Vector &b);
a questo punto il compilatore riesce a fare:
tuoPippo  operator+(Pippo(mioInt), mioPippo);
Ovviamente tutto questo se vuoi rendere l'operazione
di somma simmetrica. Se non la volessi fare simmetrica
vai contro l'etica che dice di "non cambiare semantica
agli operatori".
Un altro consiglio: quando si implementa un operatore algebrico
e' preferibile tornare un valore const!!!!
const Vector operator+(const Vector &a, const Vector &b);
in questo modo puoi fare il detect a compile time di brutture del tipo:
a + b  c;



come ti e' stato detto non puoi passare per reference
non const degli oggetti temporanei, in questo caso
l'oggetto temporaneo e' il risultato della differenza.
(*a - f->center()).
Ti consiglio di riscrivere il tuo codice con le seguenti modifiche:
=====================================================
void multLookAt(Vector &eye, Vector &at, Vector &up);
diventa:
void multLookAt(const Vector &eye, const Vector &at, const Vector &up);
(mi sa tanto che ti serve per la lookAt di OpenGL :-) )
=====================================================
Vector &center() { return m_center; }
diventa:
const Vector &center() const { return m_center; }
=====================================================
friend inline Vector operator ? (Vector &a, Vector &b);
diventa:
friend inline const Vector operator ? (const Vector &a, const Vector &b);
(con ? intendo tutti gli operatori)
=====================================================
PS. Sei stai usando questo vettore per operazioni di grafica (la multLookAt
lo lascia intuire) ti consiglio di estendere questa classe affinche abbia 4
elementi. Puoi gestire con la stessa scalature ( hai fatto del resto
gli operatori: friend inline Vector operator*(Vector &a, float coeff);
e  friend inline Vector operator*(float coeff, Vector &a); )
traslazioni e rotazioni.


 sia discutibile può anche essere. Ma scorretta non lo è di certo.
Discutibile? Io la preferisco, anzi me la impongo ( a meno di casi
particolari).


 particolari).
Concordo. trattandosi di un'operazione simmetrica preferisco mantenere
la simmetria.
Ciao,
Nicola

 Vengo e mi spiego.
Grazie per il chiarimento.
Cecco

particolari).
Io mi impongo di stare il più lontano possibile dall'unso di friend se
non in casi estremamente rari e di scrivere non meno di una pagina di
documentazione per ogni uso di friend in cui si dimostra che non era
evitabile.


agli operatori".
Poi due mesi dopo ti accorgi che esiste anche operator* che č definito
solo fra due vector ma non č definito fra vector e int (o peggio, visto
che dici di poter convertire un int in un vector, che esistono due
operator* diversi a secondo che si tratti di un int o un di un vector
costruito da un int).
Io faccio cosě (e tra l'altro evito l'uso di friend):
// caso in cui un Vector *E'* un int (non semplicemente
// che ha senso costruire un Vector partendo da un int
// ma proprio che un Vector sia un int):
class Vector {
public:
    Vector();
    Vector(const Vector&);
    Vector(int);
    virtual ~Vector();
    Vector&operator=(const Vector&);
    Vector&operator=(int);
    const Vector operator+(const Vector&) const;
    const Vector operator+(int) const;
};
const Vector operator+(int, const Vector&);
// caso, molto piů probabile, in cui un Vector č un vector
// e un int č un int. E' possibile costruire un Vector
// inizializzandolo con un int.
class Vector {
public:
    Vector();
    Vector(const Vector&);
    explicit Vector(int);
    virtual ~Vector();
    Vector&operator=(const Vector&);
    const Vector operator+(const Vector&) const;
    const Vector operator+(int) const;
};
const Vector operator+(int, const Vector&);
Ciao
Gaetano.


sia discutibile può anche essere.
Perché? Non capisco.
-- 
                    Marco Barisione (ICQ 84277092)
Se sei interessato alla creazione di it.comp.lang (per i linguaggi di
programmazione senza un gruppo apposito e per i confronti fra i
linguaggi) partecipa alla discussione sul manifesto su it.news.gruppi.

 evitabile.
nel caso specifico basta definire operator += come membro e operator +
in funzione del precedente.
Ciao,
Nicola

 evitabile.
Io inveve mi impongo di non cambiare il comportamento degli
operatori algebrici, in questo caso l'addizione e' un'operazione
simmetrica e come tale la voglio lasciare.
Be le tre righe di cui sopra sono meno di una pagina e penso che
siano sufficienti.


 
quindi insomma devi implementare tre volte ogni operatore
per non parlare del fatto che se vuoi trattare una classe
come se fosse un altro oggetto (gli hai fatto il costruttore, che
io alcune volte chiamo "costruttore di conversione")
devi riimplementare tutti gli operatori...
Discutibile.


perchè la notazione "a + b" in pratica è equivalente a "a.operator+(b)".
No, questa regola vale solo per gli operatori membro, ed in questo
caso non lo era.
Il Ghenio
-----
These are my parting words to you:
those who give up are doomed!
-----

 Discutibile.
In caso di simmetria completa due operatori sono implementati in
funzione del terzo (e di un costruttore di conversione).
I vantaggi sono: non uso friend e quindi non bypasso l'encapsulation e
inoltre tengo separati i vari operatori (matematicamente una funzione
VxV->V è diversa da una VxI->V e da una IxV->V anche se puoi definire le
altre due basandoti sulla prima (arricchita da una conversione da I a
V).
Con questo meccanismo rendi esplicito cosa succede e lo incapsuli in
apposite operazioni. Se in un futuro uno dei tuoi assunti iniziali
(simmetria, implementazione di VxI come conversione di I in V etc.)
dovesse risultare errato oppure cambiare avresti l'infrastruttura
corretta per intervenire.
Non ho capito in che senso dici che devo reimplementare tutti gli
operatori.


 operatori.
Se introduci un altro "costruttore di conversione" devi riimplementare
tutti gli operatori per il nuovo tipo.


tutti gli operatori per il nuovo tipo.
Infatti IMO quel costruttore di conversione non ci dovrebbe essere. Non
so cosa tu faccia in quel costruttore e perchè ce lo hai messo. Il nome
della classe è Vector ma non so tu cosa intendi con "Vector". Se intendi
quello che intendo io allora un Vector non è un intero e quel
costruttore dovrebbe avere la clausola explicit per IMPEDIRE quello che
tu invece vuoi che avvenga.
Ma naturalmente tutto dipende da cosa intendi con Vector.
In ogni caso quando disegni una classe non la devi disegnare in modo che
permetta solo l'uso secondo le specifiche attuali ma, per quanto
posssibile, che permetta una reimplementazione in base a specifiche
diverse senza "impattare" (sempre per quanto possibile) codice
esistente.
--
Posted from 
nts-03.mm.eutelsat.org
 [194.214.173.15]
via Mailgate.ORG Server - 
http://www.Mailgate.ORG
--


 simmetrica.
Fermo.
Date due matrici A e B allora A x B e' diverso da B x A
(sempre che le due matrici siano quadrate e della stessa dimensione,
altrimenti non puoi invertire il prodotto).
pero' se moltiplichi un float "a" per una matrice questo vuol dire che
vuoi "scalare la matrice" e lo ottieni con una matrice diagonale
con "a" sulla diagonale (era quello che voleva fare l' OP ad esempio)
e a questo punto I(a) * B  B * I(a).
Quindi in questo caso (delle matrici) io farei:
1) costruttore di matrice con float (che genera una matrice diagonale)
2) operatore friend simmetrico per  float * Matrice  Matrice * float
nel caso del OP deve solo usare un vettore con "coordinata aggiunta"
(4 dimensioni per intenderci) e poi usare il vector "misto" con i float.
Ciao
Gaetano.
PS. Sappiamo entrambi cosa "voglia" dire l'altro e la verita' come avviene
      in questi casi non sta da entrambi le parti.


      in questi casi non sta da entrambi le parti.
Volevi dire che non sta totalmente da una parte, suppongo. La verità sta
da entrambe le parti solo se diciamo la stessa cosa!
In ogni caso non sto dicendo che la tua soluzione è sbagliata. Solo che
è meno efficiente in caso di cambiamenti nelle specifiche (e IMO anche
leggermente meno leggibile).


 leggermente meno leggibile).
Fammi un esempio di cambiamento di specifica nella quale tu non devi
fare i salti mortali ed invece io devi ri iniziare da un "BrainStorm".
Ciao
Gaetano.


 void multLookAt(const Vector &eye, const Vector &at, Vector &up);
in questo caso saranno tutti e tre const, in quanto i tre vettori sono 
immutabili per tutta l'esecuzione della funzione.
ho cercato su google qualche riferimento piu' dettagliato, ma ho trovato 
abbastanza poco. Qualcuno ha un buon link (non libro) che spieghi in 
dettaglio l'argomento ?

 la durata della funzione multLookAt) come potrei fare?
basta passarlo per copia:
void multLookAt(Vector eye, Vector at, Vector up);


fare i salti mortali ed invece io devi ri iniziare da un "BrainStorm".
Senza cambiare le specifiche, basta che motivi di prestazioni runtime ti
portino a scoprire che implementare lo scaling come moltiplicazione per
una matrice diagonale non è sufficientemente prestante e diventi
necessario implementare il codice dello scalingo in maniera ottimizzata
rispetto alla generica moltiplicazione fra matrici.
In termini di cambio di specifiche è sufficiente che ti venga chiesto di
prevedere vari modi di inizializzare una matrice partendo da un float
(per esempio prevedere un "fill" della matrice con tutti i valori uguali
in alternativa alla creazione della diagonale). Oppure che ti venga
detto che è prevista la possibilità di moltiplicare la matrice per un
oggetto X diverso sia dalla matrice che dal float e che questo oggetto
sia esso stesso costruibile da un float. Esempio semplice il prodotto
fra matrice e vettore.
La mia soluzione sarebbe:
class Matrix;
class Vector {
    Vector& fill(float f);
public:
    Vector();
    Vector(const Vector&);
    virtual ~Vector();
    Vector& operator=(const Vector&);
    static const Vector filled(float f); // return Vector().fill(f);
    const Vector operator*(const Vector&) const;
    const Vector operator*(const Matrix&) const;
    const Vector operator*(float f) const; // scaling
};
const Vector operator*(float f, const Vector& v); // return v*f;
class Matrix {
    Matrix& fill(float f);
    Matrix& diag(float f);
public:
    Matrix();
    Matrix(const Matrix&);
    virtual ~Matrix();
    Matrix& operator=(const Matrix&);
    static const Matrix filled(float f); // return Matrix().fill(f);
    static const Matrix diagonal(float f); // return Matrix().diag(f);
    const Matrix operator*(const Matrix&) const;
    const Vector operator*(const Vector&) const;
    const Matrix operator*(float f) const; // scaling
};
const Matrix operator*(float f, const Matrix& m); // return m*f;
// nota:
// const Matrix Matrix::operator*(float f) const;
// può essere implementato come "return operator*(Matrix::diagonal(f));"
// ma questa implementazione può non essere sufficientemente efficiente
Nel tuo caso avresti due problemi. Uno il fatto che non puoi distinguere
in termini di costruttore fra il fill e lo scaling. L'altro (più grave)
è che potresti avere un costruttore di Vector che prende un float
(qualunque cosa significhi, e se non lo hai potresti volerlo aggiungere
in un secondo tempo). A questo punto non potresti più distinguere fra:
const Matrix operator*(const Matrix& m1, const Matrix& m2);
e
const Vector operator*(const Matrix& m, const Vector& v);
quando moltiplichi una matrice per un float.
--
Posted from 
nts-03.mm.eutelsat.org
 [194.214.173.15]
via Mailgate.ORG Server - 
http://www.Mailgate.ORG
--


la durata della funzione multLookAt) come potrei fare?
La cosa migliore č passare per valore. Altrimenti puoi fare un cast
verso lo stesso tipo non costante:
void multLookAt(const Vector &eye, const Vector &at, Vector &up)
{
  Vector& eye_t = (Vector&)eye;
  // ...
Non č il massimo dell'eleganza, ma non dovrebbe dare problemi.
-- 
Ciao, Marvin

 dettaglio l'argomento ?
Quale argomento? Le reference? Il const? Direi che dovrebbe parlarne
qualunque buon reference (FAQ?). Naturalmente *buon* esclude schildt.


 Non è il massimo dell'eleganza, ma non dovrebbe dare problemi.
Eccome invece. Se l'oggetto passato era dichiarato const di per se hai
un undefined behaviour. Se non lo era, corri il rischio cje eye, at e up
siano alias di uno stesso oggetto. E comunque stai modificando un
oggetto del quale non conosci la lifetime.
Sono molto rari i casi in cui un cast che rimuove const sono necessari.
Io consiglio di evitarli il più possibile e di documentarli bene (e di
renderli riconoscibili ad una "find" usando const_cast).


Sono molto rari i casi in cui un cast che rimuove const sono necessari.
Nel caso proposto dall'OP, l'unico motivo è l'efficienza. In compenso,
si crea una potenziale fonte di bug.
-- 
Ciao, Marvin

 quando moltiplichi una matrice per un float.
Oggi in B0027 (vicino la caffetteria) Brainstorm alle 15:00.
Ciao
Gaetano.
