Pregunta

Estoy bastante seguro de que esto es posible, porque estoy bastante seguro de que lo he visto hecho. Creo que es increíble, pero con mucho gusto aceptaré respuestas en la línea de & "; Esta es una idea terrible porque ____ &";

Digamos que tenemos una estructura básica.

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

Ahora, quiero implementar alias en estas variables.

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 tercera sintaxis sería indistinguible de una matriz. Es decir, si envié arr como parámetro de referencia a una función que espera una matriz de flotantes en la que almacenará datos (por ejemplo, muchas de las funciones de OpenGL glGet), funcionaría bien.

¿Qué te parece? ¿Posible? ¿Posible pero estúpido?

¿Fue útil?

Solución

Lo que haría es hacer accesos:

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_;
}

Otros consejos

Las estructuras anidadas sin nombre en una unión no son C ++ estándar. Esto, sin embargo, debería funcionar:

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};

EDITAR: Un poco más de información. La estructura utiliza una matriz de 3 miembros de puntero a datos para acceder a los datos en los operadores sobrecargados [].

La línea " typedef float Vertex :: * const vert " significa que vert es un puntero a un miembro flotante de la estructura Vertex. El [3] significa que es una matriz de 3 de estos. En el operador sobrecargado [], esta matriz se indexa y el puntero al miembro de datos se desreferencia y se devuelve el valor.

Además, este método debería funcionar independientemente de los problemas de empaque: el compilador es libre de rellenar la estructura Vertex como quiera y seguirá funcionando bien. Una unión anónima tendrá problemas si los flotadores se empaquetan de manera diferente.

¿Usar una unión?

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

No lo recomendaría, generará confusión.


Agregado :

Como señaló Adrian en su respuesta, esta unión con miembros anónimos de estructura no es compatible con ISO C ++. Funciona en GNU G ++ (con quejas por no ser compatible cuando activa '-Wall -ansi -pedantic'). Es una reminiscencia de los días C pre-pre-estándar (pre-K & Amp; R 1st Edn), cuando los nombres de los elementos de la estructura tenían que ser únicos en todas las estructuras, y podría usar anotaciones contratadas para llegar a un desplazamiento dentro de la estructura, y podría usar nombres de miembros de otros tipos de estructuras, una forma de anarquía. Cuando comencé a usar C (hace mucho tiempo, pero después de K & Amp; R1), eso ya era un uso histórico.

La notación que se muestra con miembros de unión anónimos (para las dos estructuras) es compatible con C11 (ISO / IEC 9899: 2011), pero no con versiones anteriores del estándar C. La Sección 9.5 de ISO / IEC 14882: 2011 (C ++ 11) proporciona uniones anónimas, pero GNU g++ (4.9.1) no acepta el código que se muestra con -pedantic, identificando & Quot; warning: ISO C++ prohibits anonymous structs [-Wpedantic] " ;.

Dado que la idea conducirá a la confusión, no estoy particularmente preocupado de que no sea estándar; No usaría el mecanismo para esta tarea (y desconfiaría de usar estructuras anónimas en un sindicato, incluso si fuera beneficioso).


Se planteó una preocupación:

  

Los tres (x-y-z, r-g-b y la matriz) no se alinean necesariamente.

Es una unión con tres elementos; Los tres elementos comienzan en la misma dirección. Los dos primeros son estructuras que contienen 3 valores flotantes. No hay herencia y no hay funciones virtuales para dar diferentes diseños, etc. Las estructuras se diseñarán con los tres elementos contiguos (en la práctica, incluso si el estándar permite el relleno). La matriz también comienza en la misma dirección, y sujeto a "sin relleno" en las estructuras, los elementos se superponen a las dos estructuras. Realmente no veo que haya un problema.

Puede obtener esto con un sindicato como otros han mencionado. La sobrecarga de color y posición en la misma estructura como esta puede no ser una buena idea (por ejemplo, agregar dos colores generalmente significa que desea saturar a 1.0, mientras que agregar vectores ocurre linealmente), pero superponer un flotador [] encima de ellos como eso está perfectamente bien y es un medio bien aceptado de intercambiar datos con GL / DirectX / etc.

Sin embargo, le recomiendo que evite referirse al mismo miembro por diferentes alias en el mismo ámbito de función, ya que esto lo llevará a un puesto de hardware desagradable llamado load-hit-store. En particular, evite esto si puede:

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

¿Referencias?

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;
};

Supongo que puedes hacer un poco de macro magia para obtener lo que quieres. Pero eso se verá feo. ¿Por qué quieres usar la misma estructura, vértice para 3 tipos diferentes? ¿Por qué no puedes definir la clase para el color? También tenga en cuenta que el vértice y el color no son lo mismo. Si cambia algo a vértice, eso también afectará el color, si tiene la misma clase para ambos.

No estoy seguro de haber entendido la pregunta correctamente. Pero parece que necesita sobrecargar el operador [] para proporcionar acceso de tipo matriz a su estructura / clase. Vea el ejemplo mencionado aquí: Sobrecarga del operador

La siguiente estructura tendrá el comportamiento solicitado:

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];
    }
};

Mala idea en mi opinión, al menos para el ejemplo dado: la desventaja es que, para casi cualquier solución a esto, probablemente podrá asignar libremente & "; rgb &" ; instancias a / desde " xyz " instancias, que probablemente rara vez sea sensata o correcta. es decir, corre el riesgo de renunciar a algún tipo de seguridad útil.

Personalmente, para el ejemplo que da, subclase los tipos rgb y xyz de una base boost::array<float,3> o similar. Entonces, ambos heredan el operador [], se pueden pasar a funciones que esperan matrices, y se pasan con más seguridad de tipo a cosas que esperan colores / coordenadas. Es frecuente que desee tratar un xyz o un rgb como una matriz, pero es raro que desee tratar un xyz como un rgb o viceversa. (rgb IS-A array: OK. xyz IS-A array: OK. rgb IS-A xyz ???? ¡No lo creo!)

Por supuesto, eso significa acceso a x, y, z & amp; r, g, b debe ser por el descriptor de acceso (reenvío al operator[](...) apropiado) en lugar de directamente al miembro. (Necesitaría las propiedades de C # para eso).

Tengo una plantilla y dos clases de vectores a continuación, una loca, otra cuerda. La plantilla implementa una matriz simple de valores fijos en tiempo de compilación. Está diseñado para subclasificar y utiliza una variable de matriz protegida para evitar que tenga que saltar a través de aros para acceder a la matriz. (A algunas personas podría no gustarles tal diseño. Digo, si sus subclases están llamando a sus operadores sobrecargados, el acoplamiento puede ser una buena idea).

La clase loca le permite tener variables miembro llamadas x, y, z y actúa como una matriz para llamadas a glGetFloatV. El cuerdo solo tiene funciones de acceso x (), y (), z () y aún funciona con glGetFloatV. Puede usar cualquiera de las clases como base para otros objetos vectoriales que podría pasar a la biblioteca OpenGL. Aunque las siguientes clases son específicas de los puntos, obviamente puede hacer una búsqueda / reemplazo para convertirlas en clases de color rgb.

La clase loca es loca porque el costo del azúcar sintáctico vec.x en lugar de vec.x () es de 3 variables de referencia. Eso podría ocupar mucho espacio en una aplicación grande. Use la versión más simple y sensata.

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);

Puede intentar agregar referencias a variables, como esta:

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

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

Pero su estructura se hace más grande (de 12 bytes a 40 bytes).

Para usar [] en él, use la sobrecarga del operador [], como se mencionó anteriormente.

Solo una advertencia sobre el uso de miembros de referencia que apuntan a miembros de valor. Debe definir un constructor de copia (y posiblemente también un operador de asignación), si alguna vez copia dicho objeto (como transferirlo por valor). El constructor de copias predeterminado lo dejará con una copia cuyos miembros de referencia apuntan a los miembros de valor del objeto original, no a los del nuevo objeto. Esto ciertamente no es algo que quieras.

Teniendo en cuenta que también terminas con objetos más grandes, como ya se señaló, creo que usar métodos de acceso es preferible a los miembros de referencia.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top