«Ретроактивный союз» - это можно сделать? - PullRequest
3 голосов
/ 07 сентября 2011

У меня есть два класса: шаблонный класс и обычный класс, который наследует от него:

template <int N> class Vector
{
    float data[N];
    //etc. (math, mostly)
};

class Vector3 : public Vector<3>
{
    //Vector3-specific stuff, like the cross product
};

Теперь я хотел бы иметь переменные-члены x / y / z в дочернем элементе.класс (полноправные члены, а не только получатели - я хочу иметь возможность установить их также).Но чтобы убедиться, что вся (унаследованная) математика работает, x должен ссылаться на ту же память, что и data[0], y до data[1] и т. Д. По сути, я хочу объединение, но я могуне объявляйте один в базовом классе, потому что я не знаю число чисел с плавающей точкой в ​​векторе в этот момент.

Итак, можно ли это сделать?Есть ли какая-то магия препроцессора / typedef / template, которая достигнет того, что я ищу?

PS: я использую g ++ 4.6.0 с -std = c ++ 0x, если это поможет.

Редактировать: Несмотря на то, что ссылки дадут искомый синтаксис, идеальное решение не сделает класс больше (А ссылки делают - много! A Vector<3>12 байтов. A Vector3 со ссылками - 40!).

Ответы [ 6 ]

8 голосов
/ 07 сентября 2011

Как насчет:

class Vector3 : public Vector<3>
{
public:
  // initialize the references...
  Vector3() : x(data[0]), y(data[1]), z(data[2]){}
private:
  float& x;
  float& y;
  float& z;
};

Конечно, если вы хотите, чтобы они занимали то же пространство , тогда это другая история ...

Снемного магии шаблонов, вы можете сделать следующее ...

#include <iostream>

template <int N, typename UnionType = void*> struct Vector
{
    union
    {
      float data[N];
      UnionType field;
    };

    void set(int i, float f)
    {
      data[i] = f;
    }

    // in here, now work with data
    void print()
    {
      for(int i = 0; i < N; ++i)
        std::cout << i << ":" << data[i] << std::endl;
    }
};

// Define a structure of three floats
struct Float3
{
  float x;
  float y;
  float z;
};

struct Vector3 : public Vector<3, Float3>
{
};

int main(void)
{
  Vector<2> v1;
  v1.set(0, 0.1);
  v1.set(1, 0.2);
  v1.print();

  Vector3 v2;
  v2.field.x = 0.2;
  v2.field.y = 0.3;
  v2.field.z = 0.4;
  v2.print();

}

РЕДАКТИРОВАТЬ: прочитав комментарий, я понимаю, что я публиковал ранее, на самом деле ничем не отличается, так что небольшое изменение предыдущей итерации, чтобы обеспечить прямойдоступ к полю (что, как я полагаю, вам нужно) - я полагаю, что разница между этим решением Роба и приведенным ниже решением состоит в том, что вам не нужны все специализации, чтобы снова и снова реализовывать всю логику ...

7 голосов
/ 07 сентября 2011

Как насчет специализации шаблонов?

template <int N> class Vector
{
  public:
  float data[N];
};

template <>
class Vector<1>
{
  public:
  union {
    float data[1];
    struct {
      float x;
    };
  };
};

template <>
class Vector<2>
{
  public:
  union {
    float data[2];
    struct {
      float x, y;
    };
  };
};


template <>
class Vector<3>
{
  public:
  union {
    float data[3];
    struct {
      float x, y, z;
    };
  };
};

class Vector3 : public Vector<3>
{
};

int main() {
  Vector3 v3;
  v3.x;
  v3.data[1];
};

РЕДАКТИРОВАТЬ Хорошо, здесь другой подход, но он вводит дополнительный идентификатор.

template <int N> class Data
{
  public:
  float data[N];
};

template <> class Data<3>
{
  public:
  union {
    float data[3];
    struct {
      float x, y, z;
    };
  };
};

template <int N> class Vector
{
  public:
  Data<N> data;
  float sum() { }
  float average() {}
  float mean() {}
};

class Vector3 : public Vector<3>
{
};

int main() {
  Vector3 v3;
  v3.data.x = 0; // Note the extra "data".
  v3.data.y = v3.data.data[0];
};
3 голосов
/ 07 сентября 2011

Вот одна из возможностей, подкрепленная моим ответом на этот вопрос :

class Vector3 : public Vector<3>
{
public:
    float &x, &y, &z;

    Vector3() : x(data[0]), y(data[1]), z(data[2]) { }
};

У этого есть некоторые проблемы, например, требуется указать собственный конструктор копирования, оператор присваивания и т. Д.

1 голос
/ 07 сентября 2011

Некоторое время назад я писал (что также допускало использование методов получения / установки), но это был такой непереносимый хакерский взлом, что ВЫ ДЕЙСТВИТЕЛЬНО НЕ ДОЛЖНЫ ЭТОГО . Но я думал, что все равно выброшу. В основном, он использует специальный тип с 0 данными для каждого члена. Затем функции-члены этого типа получают указатель this, вычисляют положение родительского элемента Vector3 и затем используют элементы Vector3 s для доступа к данным. Этот хак работает более или менее как ссылка, но не требует дополнительной памяти, не имеет проблем с переустановкой, и я уверен, что это неопределенное поведение, поэтому оно может вызвать носовые демоны .

class Vector3 : public Vector<3>
{
public: 
    struct xwrap {
        operator float() const;
        float& operator=(float b);
        float& operator=(const xwrap) {}
    }x;
    struct ywrap {
        operator float() const;
        float& operator=(float b);
        float& operator=(const ywrap) {}
    }y;
    struct zwrap {
        operator float() const;
        float& operator=(float b);
        float& operator=(const zwrap) {}
    }z;
    //Vector3-specific stuff, like the cross product 
};
#define parent(member) \
(*reinterpret_cast<Vector3*>(size_t(this)-offsetof(Vector3,member)))

Vector3::xwrap::operator float() const {
    return parent(x)[0];
}
float& Vector3::xwrap::operator=(float b) {
    return parent(x)[0] = b;
}
Vector3::ywrap::operator float() const {
    return parent(y)[1];
}
float& Vector3::ywrap::operator=(float b) {
    return parent(y)[1] = b;
}
Vector3::zwrap::operator float() const {
    return parent(z)[2];
}
float& Vector3::zwrap::operator=(float b) {
    return parent(z)[2] = b;
}
1 голос
/ 07 сентября 2011

Вы можете сделать следующее:

template <int N> struct Vector
{
    float data[N];
    //etc. (math, mostly)
};
struct Vector3_n : Vector<3>
{
    //Vector3-specific stuff, like the cross product
};
struct Vector3_a
{
    float x, y, z;
};
union Vector3
{
    Vector3_n n;
    Vector3_a a;
};

Сейчас:

Vector3 v;
v.n.CrossWhatEver();
std::cout << v.a.x << v.a.y << v.a.z

Вы можете попробовать анонимный трюк объединения, но это не является ни стандартным, ни очень переносимым.

Но учтите, что при таком объединении слишком просто впасть в неопределенное поведение, даже не заметив этого.Тем не менее, в любом случае это, вероятно, будет работать в основном.

0 голосов
/ 18 февраля 2012

Завершить старый вопрос: Нет. Мне грустно, но вы не можете этого сделать.

Вы можете подобраться ближе.Такие вещи, как:

Vector3.x() = 42;

или

Vector3.x(42);

или

Vector3.n.x = 42;

или даже

Vector3.x = 42; //At the expense of almost quadrupling the size of Vector3!

находятся в пределах досягаемости (см. Другиеответы - они все очень хорошие).Но мой идеал

Vector3.x = 42; //In only 12 bytes...

просто нереализуем.Нет, если вы хотите унаследовать все свои функции от базового класса.

В конце концов, рассматриваемый код в конечном итоге получил довольно незначительные изменения - теперь это строго 4-членные векторы (x, y, z,w), использует SSE для векторной математики и имеет несколько классов геометрии (Point, Vector, Scale и т. д.), поэтому наследование основных функций больше не является опцией из-за корректности типа.Так и происходит.

Надеюсь, это спасет кого-то еще от нескольких дней разочарованного поиска!

...