用C ++编写通用的Vector类

时间:2014-01-09 23:00:37

标签: c++ templates vector

我正在尝试实现一个Vector类,用于我的图形项目。我想按长度和类型对矢量进行模板化,我希望能够使用A.x,A.y,A.z,A.w来访问矢量A的成员(例如)。

这是我的第一次尝试。我绝对不是C ++模板的专家!我试图用Vec2,Vec3和Vec4的专用版本来实现一般的Vector类。每个专业类都有一个联合,允许我使用它们的名称访问坐标。不幸的是,我无法弄清楚如何在不重新实现每个专用类的每个向量函数的情况下执行此操作。

请记住,我想实现一些仅适用于特定长度的向量的函数。例如,cross product仅适用于vec3,但dot product(或operator *)适用于所有长度的矢量。

#include <cstdint>

using namespace std;

//*********************************************************************
// Vector implementation parameterized by type and size.
//*********************************************************************

template <typename T, size_t SIZE> 
class Vector {

    public:
        T data[SIZE];
        size_t size;

        Vector(T* arr);
};

template <typename T, size_t SIZE> Vector<T, SIZE>::Vector(T* arr) {
    size = SIZE;

    for(int i=0; i<size; i++) {
        data[i] = arr[i];
    }

}

//*********************************************************************
// Vec2 is a specialization of Vector with length 2.
//*********************************************************************

typedef Vector<float, 2> Vec2f;
typedef Vector<int, 2> Vec2d;

template <typename T>
class Vector <T, 2> {

    public:
        union {
            T data[2];
            struct {
                T x;
                T y;
            };
        };

        size_t size;

        Vector(T x, T y);
};

template <typename T> Vector<T, 2>::Vector(T x, T y) {
    data[0] = x; data[1] = y;
    size = 2;
}

//*********************************************************************
// Vec3 is a specialization of Vector with length 3.
//*********************************************************************

typedef Vector<float, 3> Vec3f;
typedef Vector<int, 3> Vec3d;

template <typename T>
class Vector <T, 3> {

    public:
        union {
            T data[3];
            struct {
                T x;
                T y;
                T z;
            };
        };

        size_t size;

        Vector(T x, T y, T z);
};

template <typename T> Vector<T, 3>::Vector(T x, T y, T z) {
    data[0] = x; data[1] = y; data[2] = z;
    size = 3;
}

//*********************************************************************
// Vec4 is a specialization of Vector with length 4.
//*********************************************************************

typedef Vector<float, 4> Vec4f;
typedef Vector<int, 4> Vec4d;

template <typename T>
class Vector <T, 4> {

    public:
        union {
            T data[4];
            struct {
                T x;
                T y;
                T z;
                T w;
            };
        };

        size_t size;

        Vector(T x, T y, T z, T w);
};

template <typename T> Vector<T, 4>::Vector(T x, T y, T z, T w) {
    data[0] = x; data[1] = y; data[2] = z; data[3] = w;
    size = 4;
}

1 个答案:

答案 0 :(得分:5)

避免在多个特化中重复实现相同功能的常用解决方法是从公共基类继承,并在基础中实现这些功能:

template <typename T>
struct VectorBase {
  // common stuff
};

template <typename T, std::size_t N>
struct Vector : VectorBase<T> {
  // ...
};

template <typename T>
struct Vector<T, 2> : VectorBase<T> {
  // ...
};

template <typename T>
struct Vector<T, 3> : VectorBase<T> {
  // ...
  friend Vector<T, 3> cross(const Vector<T, 3>&, const Vector<T, 3>&);
};

您将遇到的下一个问题是需要从公共基础访问派生类中的成员(例如,获取xsize()的值)。您可以使用Curiously Recurring Template Pattern (CRTP)

执行此操作
template <typename T, typename CRTP>
struct VectorBase {
  CRTP& crtp() { return static_cast<CRTP&>(*this); }
  const CRTP& crtp() const { return static_cast<const CRTP&>(*this); }

  std::size_t size() const {
    return std::extent<decltype(CRTP::data)>::value;
  }

  void zero() {
    std::fill(std::begin(crtp().data), std::end(crtp().data), T());
  }

  using iterator = T*;
  using const_iterator = const T*;
  iterator begin() { return &crtp().data[0]; }
  iterator end() { return &crtp().data[0] + size(); }
  const_iterator begin() const { return &crtp().data[0]; }
  const_iterator end() const { return &crtp().data[0] + size(); }

  T& operator [] (std::size_t i) {
    return crtp().data[i];
  }
  const T& operator [] (std::size_t i) const {
    return crtp().data[i];
  }
};

template <typename T, std::size_t N>
struct Vector : VectorBase<T, Vector<T, N>> {
  union {
    T data[N];
    struct {
      T x, y, z, w;
    };
  };
};

template <typename T>
struct Vector<T, 2> : VectorBase<T, Vector<T, 2>> {
  union {
    T data[2];
    struct {
      T x, y;
    };
  };
};

template <typename T>
struct Vector<T, 3> : VectorBase<T, Vector<T, 3>> {
  union {
    T data[3];
    struct {
      T x, y, z;
    };
  };
};

template <typename T, typename U, std::size_t N>
auto operator * (const Vector<T, N>& a, const Vector<U, N>& b)
 -> Vector<decltype(a[0] * b[0]), N> {
  Vector<decltype(a[0] * b[0]), N> result;
  for (std::size_t i = 0; i < N; ++i) {
    result[i] = a[i] * b[i];
  }
  return result;
}

template <typename T, typename U, std::size_t N>
auto dot(const Vector<T, N>& a, const Vector<U, N>& b)
 -> decltype(a[0] * b[0]) {
  auto product = a * b;
  using V = decltype(product.x);
  return std::accumulate(std::begin(product), std::end(product), V(0));
}

** Sample Code at Coliru **

评论者引用的未定义行为有两个问题:

  1. 匿名结构(例如struct { T x, y, z; };)是GNU扩展,因此可能只适用于GCC和兼容的编译器(clang)。

  2. 从上次存储的成员以外的工会成员中读取通常是未定义的行为;这个特定的例子至少是边界的,因为所涉及的每个类型都是标准布局,并且所有读/写的值都是相同的类型。我会让其他人做准确的语言律师,并简单地声明在使用支持匿名结构扩展的最新编译器时,代码几乎肯定会按预期执行。

  3. 如果这些非标准要求中的任何一个困扰您,请删除结构和联合,以便该数组是唯一的数据成员。然后为符号名称添加函数,例如T& x() { return data[0]; },它只是稍微麻烦一点。