Domanda

Sono abbastanza sicuro che sia possibile, perché sono abbastanza sicuro di averlo visto fatto. Penso che sia fantastico, ma accetterò volentieri le risposte in linea con & Quot; questa è un'idea terribile perché ____ & Quot ;.

Supponiamo di avere una struttura di base.

struct vertex
{
    float x, y, z;
};

Ora, voglio implementare gli alias su queste variabili.

vertex pos;
vertex col;
vertex arr;

pos.x = 0.0f; pos.y = 0.5f; pos.z = 1.0f;
col.r = 0.0f; col.g = 0.5f; col.b = 1.0f;
arr[0] = 0.0f; arr[1] = 0.5f; arr[2] = 1.0f;

Idealmente la terza sintassi sarebbe indistinguibile da un array. Cioè, se avessi inviato arr come parametro di riferimento a una funzione in attesa di una matrice di float in cui verranno archiviati i dati (ad esempio molte delle funzioni di OpenGL glGet), funzionerebbe bene.

Cosa ne pensi? Possibile? Possibile ma stupido?

È stato utile?

Soluzione

Quello che vorrei fare è rendere gli accessor:

struct Vertex {
    float& r() { return values[0]; }
    float& g() { return values[1]; }
    float& b() { return values[2]; }

    float& x() { return values[0]; }
    float& y() { return values[1]; }
    float& z() { return values[2]; }

    float  operator [] (unsigned i) const { return this->values_[i]; }
    float& operator [] (unsigned i)       { return this->values_[i]; }
    operator float*() const { return this->values_; }

private:
    float[3] values_;
}

Altri suggerimenti

Le strutture nidificate senza nome in un'unione non sono C ++ standard. Questo, tuttavia, dovrebbe funzionare:

struct Vertex
{
private:
   typedef float Vertex::* const vert[3];
   static const vert v;

public:
   typedef size_t size_type;
   float x, y, z;

   const float& operator[](size_type i) const {
      return this->*v[i];
   }

   float& operator[](size_type i) {
      return this->*v[i];
   }

};

const Vertex::vert Vertex::v = {&Vertex::x, &Vertex::y, &Vertex::z};

EDIT: qualche informazione in più. La struttura utilizza una matrice di 3 membri puntatore a dati per accedere ai dati negli operatori sovraccaricati [].

La riga " typedef float Vertex :: * const vert " significa che vert è un puntatore a un membro float della struttura del vertice. Il [3] significa che è un array di 3 di questi. Nell'operatore sovraccarico [], questo array viene indicizzato e il puntatore al membro dati viene referenziato e il valore restituito.

Inoltre, questo metodo dovrebbe funzionare indipendentemente dai problemi di impacchettamento: il compilatore è libero di riempire la struttura di Vertex come preferisce e funzionerà comunque bene. Un'unione anonima si imbatterà in problemi se i float sono imballati in modo diverso.

Usa un sindacato?

union vertex
{
    struct { float x, y, z; };
    struct { float r, g, b; };
    float arr[3];
};

Non lo consiglierei - porterà alla confusione.


Aggiunto :

Come notato da Adrian nella sua risposta, questa unione con membri strutt anonimi non è supportata da ISO C ++. Funziona in GNU G ++ (con lamentele per non essere supportato quando si attiva '-Wall -ansi -pedantic'). Ricorda i giorni C pre-standard (pre-K & Amp; R 1st Edn), quando i nomi degli elementi della struttura dovevano essere univoci in tutte le strutture e si potevano usare le notazioni contratte per ottenere un offset all'interno la struttura e potresti usare i nomi dei membri di altri tipi di struttura, una forma di anarchia. Quando ho iniziato a usare C (molto tempo fa, ma post-K & Amp; R1), era già un utilizzo storico.

La notazione mostrata con membri sindacali anonimi (per le due strutture) è supportata da C11 (ISO / IEC 9899: 2011), ma non da versioni precedenti dello standard C. La sezione 9.5 della ISO / IEC 14882: 2011 (C ++ 11) prevede i sindacati anonimi, ma GNU g++ (4.9.1) non accetta il codice mostrato con -pedantic, identificando & Quot; warning: ISO C++ prohibits anonymous structs [-Wpedantic] quot &;.

Poiché l'idea porterà alla confusione, non mi preoccupo particolarmente che non sia standard; Non userei il meccanismo per questo compito (e sarei diffidente nell'usare strutture anonime in un sindacato anche se fosse utile).


È stata sollevata una preoccupazione:

  

I tre (x-y-z, r-g-b e l'array) non si allineano necessariamente.

È un'unione con tre elementi; i tre elementi iniziano con lo stesso indirizzo. I primi due sono strutture contenenti 3 valori float. Non c'è ereditarietà e non ci sono funzioni virtuali per dare layout diversi, ecc. Le strutture saranno disposte con i tre elementi contigui (in pratica, anche se lo standard consente il riempimento). L'array inizia anche allo stesso indirizzo e soggetto a "nessuna imbottitura" nelle strutture, gli elementi si sovrappongono alle due strutture. Non vedo davvero che ci sarebbe un problema.

Puoi ottenerlo con un sindacato come altri hanno già detto. Sovraccaricare il colore e la posizione sulla stessa struttura in questo modo potrebbe non essere una buona idea (ad esempio, aggiungere due colori di solito significa che si desidera saturare a 1.0, mentre l'aggiunta di vettori avviene in modo lineare), ma sovrapponendo un float [] sopra di essi come questo è perfettamente perfetto e un mezzo ben accettato per scambiare dati con GL / DirectX / ecc.

Ti consiglio di evitare di fare riferimento allo stesso membro con alias diversi nello stesso ambito di funzione, perché questo ti guiderà in una brutta stallo hardware chiamato load-hit-store. In particolare, evitalo se puoi:

vector foo; 
foo.x = 1.0f;
return foo[0] + foo[1];

I riferimenti?

template<typename T>
struct vertex {
    vertex() :
        r(data[0]), g(data[1]), b(data[2]),
        x(data[0]), y(data[1]), z(data[2])
    {
    }

    T *operator *() {
        return data;
    }

    const T *operator *() const {
        return data;
    }

    T data[3];
    T &r, &g, &b;
    T &x, &y, &z;
};

Suppongo che tu possa fare un po 'di macro magia per ottenere quello che vuoi. Ma sembrerà brutto. Perché vuoi usare la stessa struttura, vertice per 3 tipi diversi? Perché non riesci a definire la classe per il colore? Inoltre, tieni presente che vertice e colore non sono gli stessi. Se cambi qualcosa in un vertice, ciò influirà anche sul colore, se hai la stessa classe per entrambi.

Non sono sicuro di aver capito correttamente la domanda. Ma sembra che sia necessario sovraccaricare l'operatore [] per fornire accesso come array alla propria struttura / classe. Vedi l'esempio citato qui: Sovraccarico dell'operatore

La seguente struttura avrà il comportamento richiesto:

struct vertex
{
private:
    float data[3];
public:
    float &x, &y, &z;
    float &r, &g, &b;

    vertex() : x(data[0]), y(data[1]), z(data[2]), r(data[0]), g(data[1]), b(data[2]) {
    }

    float& operator [](int i) { 
        return data[i];
    }
};

Cattiva idea secondo me, almeno per l'esempio dato: il rovescio della medaglia è che, praticamente per qualsiasi soluzione a questo, probabilmente sarai in grado di assegnare liberamente " rgb " ; istanze a / da " xyz " istanze, che probabilmente è raramente sensibile o corretto. cioè rischi di rinunciare a qualche utile tipo di sicurezza.

Personalmente, per l'esempio che fornisci, vorrei sottoclassare i tipi rgb e xyz da una base boost::array<float,3> o simile. Quindi entrambi ereditano l'operatore [], possono essere passati a funzioni in attesa di matrici e passati con maggiore sicurezza di tipo a cose che si aspettano colori / coordinate. Spesso vuoi trattare un xyz o un rgb come un array, ma raramente vuoi trattare un xyz come un rgb o viceversa. (rgb IS-A array: OK. xyz IS-A array: OK. rgb IS-A xyz ???? Non credo proprio!)

Ovviamente questo significa accesso a x, y, z & amp; r, g, b deve essere effettuato dall'accessor (inoltro all'appropriato operator[](...)) anziché diretto al membro. (Avresti bisogno delle proprietà di C # per questo).

Ho un modello e due classi Vector sotto, una pazza, una sana. Il modello implementa un semplice array di valori fisso in fase di compilazione. È progettato per la sottoclasse e utilizza una variabile di array protetta per evitare di dover saltare attraverso i cerchi per accedere all'array. (Ad alcune persone potrebbe non piacere un simile progetto. Dico, se le tue sottoclassi chiamano i tuoi operatori sovraccarichi, l'accoppiamento potrebbe essere una buona idea.)

La classe pazza ti permette di avere variabili membro chiamate x, y, z e si comporta come un array per le chiamate a glGetFloatV. Quello sano ha solo funzioni accessor x (), y (), z () e funziona ancora con glGetFloatV. Puoi usare entrambe le classi come base per altri oggetti vettoriali che potresti passare alla libreria OpenGL. Sebbene le classi seguenti siano specifiche dei punti, puoi ovviamente fare una ricerca / sostituzione per trasformarle in classi di colori rgb.

La classe pazza è pazza perché il costo dello zucchero sintattico vec.x invece di vec.x () è di 3 variabili di riferimento. Ciò potrebbe richiedere molto spazio in un'applicazione di grandi dimensioni. Usa la versione più semplice e sana.

template <typename T, int N>
class FixedVector {
protected:
    T arr[N];
public:
    FixedVector();

    FixedVector(const T* a) {
        for (int i = 0; i < N; ++i) {
            arr[i] = a[i];
        }
    }

    FixedVector(const T& other) {
        for (int i = 0; i < N; ++i) {
            arr[i] = other.arr[i];
        }
    }

    FixedVector& operator=(const T& other) {
        for (int i = 0; i < N; ++i) {
            arr[i] = other.arr[i];
        }
        return *this;
    }

    T* operator&() { return arr; }
    const T* operator&() const { return arr; }

    T& operator[](int ofs) { 
        assert(ofs >= 0 && ofs < N);
        return arr[ofs];
    }
    const T& operator[](int ofs) const { 
        assert(ofs >= 0 && ofs < N);
        return arr[ofs];
    }
};

class CrazyPoint :  public FixedVector<float, 3> {
public:
    float &x, &y, &z;

    CrazyPoint()
      : x(arr[0]), y(arr[1]), z(arr[2])
    { arr[0] = arr[1] = arr[2] = 0.0; }

    CrazyPoint(const float* a)
      : x(arr[0]), y(arr[1]), z(arr[2])
    {
        arr[0] = a[0];
        arr[1] = a[1];
        arr[2] = a[2];
    }

    CrazyPoint(float a, float b, float c) 
      : x(a), y(b), z(c)
    {
        arr[0] = a;
        arr[1] = b;
        arr[2] = c;
    }
};

class SanePoint : public FixedVector<float, 3> {
public:
    float& x() { return arr[0]; }
    float& y() { return arr[1]; }
    float& z() { return arr[2]; }

    SanePoint() { arr[0] = arr[1] = arr[2] = 0.0; }
    SanePoint(float a, float b, float c) 
    {
        arr[0] = a;
        arr[1] = b;
        arr[2] = c;
    }
};

// usage
SanePoint normal;
glGetFloatV(GL_CURRENT_NORMAL, &normal);

Puoi provare ad aggiungere riferimenti a variabili, in questo modo:

struct test {
        float x, y, z;
        float &r, &g, &b;

        test() : r(x), g(y), b(z) {}
    };

Ma la tua struttura diventa più grande (da 12 byte a 40 byte).

Per usare [] su di esso, usa il sovraccarico dell'operatore [], come menzionato prima.

Solo un avvertimento sull'uso di membri di riferimento che puntano a membri di valore. È necessario definire un costruttore di copie (e possibilmente anche un operatore di assegnazione), se si copia un oggetto del genere (come trasferirlo per valore). Il costruttore di copie predefinito ti lascerà con una copia i cui membri di riferimento puntano ai membri del valore dell'oggetto originale, non a quelli del nuovo oggetto. Questo non è certamente qualcosa che desideri.

Considerando che anche tu finisci con oggetti più grandi, come già sottolineato, penso che usare i metodi di accesso sia da preferire ai membri di riferimento.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top