我正在编写一个带有模板矢量类型的简单数学库:
template<typename T, size_t N>
class Vector {
public:
Vector<T, N> &operator+=(Vector<T, N> const &other);
// ... more operators, functions ...
};
现在我想要一些附加功能,专门用于其中一些。假设我希望x()
上的函数y()
和Vector<T, 2>
访问特定的坐标。我可以为此创建一个部分专业化:
template<typename T>
class Vector<T, 3> {
public:
Vector<T, 3> &operator+=(Vector<T, 3> const &other);
// ... and again all the operators and functions ...
T x() const;
T y() const;
};
但现在我正在重复通用模板中已经存在的所有内容。
我也可以使用继承。将通用模板重命名为VectorBase
,我可以这样做:
template<typename T, size_t N>
class Vector : public VectorBase<T, N> {
};
template<typename T>
class Vector<T, 3> : public VectorBase<T, 3> {
public:
T x() const;
T y() const;
};
但是,现在问题是所有运算符都在VectorBase
上定义,因此它们返回VectorBase
个实例。这些不能分配给Vector
变量:
Vector<float, 3> v;
Vector<float, 3> w;
w = 5 * v; // error: no conversion from VectorBase<float, 3> to Vector<float, 3>
我可以给Vector
隐式转换构造函数以使其成为可能:
template<typename T, size_t N>
class Vector : public VectorBase<T, N> {
public:
Vector(VectorBase<T, N> const &other);
};
但是,现在我正在从Vector
转换为VectorBase
并再次返回。尽管类型在内存中是相同的,并且编译器可能会优化所有这些,但它感觉很笨,我真的不喜欢潜在的运行时开销,这本质上是一个编译时问题。
还有其他方法可以解决这个问题吗?
答案 0 :(得分:9)
我认为您可以使用CRTP来解决此问题。这个习语用于boost::operator。
template<typename ChildT, typename T, int N>
class VectorBase
{
public:
/* use static_cast if necessary as we know that 'ChildT' is a 'VectorBase' */
friend ChildT operator*(double lhs, ChildT const &rhs) { /* */ }
friend ChildT operator*(ChildT const &lhs, double rhs) { /* */ }
};
template<typename T, size_t N>
class Vector : public VectorBase<Vector<T,N>, T, N>
{
};
template<typename T>
class Vector<T, 3> : public VectorBase<Vector<T, 3>, T, 3>
{
public:
T x() const {}
T y() const {}
};
void test()
{
Vector<float, 3> v;
Vector<float, 3> w;
w = 5 * v;
w = v * 5;
v.x();
Vector<float, 5> y;
Vector<float, 5> z;
y = 5 * z;
y = z * 5;
//z.x(); // Error !!
}
答案 1 :(得分:4)
这是我在使用C ++ 0x功能时提出的一些问题。此处使用的唯一C ++ 0x功能是static_assert
,因此您可以使用Boost替换它。
基本上,我们可以使用静态大小检查函数,只检查以确定给定索引小于向量的大小。如果索引超出范围,我们使用静态断言来生成编译器错误:
template <std::size_t Index>
void size_check_lt() const
{
static_assert(Index < N, "the index is not within the range of the vector");
}
然后我们可以提供一个get()
方法,该方法返回给定索引处元素的引用(显然const过载也很有用):
template <std::size_t Index>
T& get()
{
size_check_lt<Index>(); return data_[Index];
}
然后我们可以像这样编写简单的访问器:
T& x() { return get<0>(); }
T& y() { return get<1>(); }
T& z() { return get<2>(); }
如果向量只有两个元素,则可以使用x和y但不能使用z。如果向量具有三个或更多元素,则可以使用这三个元素。
我最终为构造函数做了同样的事情 - 我为维度为2,3和4的向量创建了构造函数,并添加了size_check_eq
,允许它们仅针对维度为2,3的向量进行实例化,和四个。如果有人有兴趣,我可以在今晚回家时尝试发布完整的代码。
我把项目中途放弃了,所以这样做可能会有一些很大的问题,我没有遇到过......至少可以考虑这个问题。
答案 2 :(得分:0)
最简单的方法?使用外部功能:
template <class T>
T& x(Vector<T,2>& vector) { return vector.at<0>(); }
template <class T>
T const& x(Vector<T,2> const& vector) { return vector.at<0>(); }
使用外部函数进行模板编程是添加功能的最简单方法,因为您刚刚遇到的特殊问题。
另一方面,您仍然可以为x
提供y
,z
和N
,或者使用enable_if
/ disable_if
限制范围的功能。
答案 3 :(得分:0)
我不知道你是否可以解决赋值运算符的输入问题,但是你可以通过定义各种运算符的模板版本,实现它们的辅助函数,然后使用继承来使生活更轻松。 / p>
template <typename T, std::size_t N>
class fixed_array {
public:
virtual ~fixed_array() {}
template <std::size_t K>
fixed_array& operator+=(fixed_array<T,K> const& other) {
for (std::size_t i=0; i<N; ++i)
this->contents[i] += other[i];
return *this;
}
template <std::size_t K>
fixed_array& operator=(fixed_array<T,K> const& other) {
assign_from(other);
return *this;
}
T& operator[](std::size_t idx) {
if (idx >= N)
throw std::runtime_error("invalid index in fixed_array[]");
return contents[idx];
}
protected:
template <std::size_t K>
void assign_from(fixed_array<T,K> const& other) {
for (std::size_t i=0; i<N; ++i)
this->contents[i] = other[i];
}
private:
T contents[N];
};
template <typename T>
class fixed_2d_array: public fixed_array<T,2> {
public:
T x_coord() const { return (*this)[0]; }
T y_coord() const { return (*this)[1]; }
template <std::size_t K>
fixed_2d_array& operator=(fixed_array<T,K> const& other) {
assign_from(other);
return *this;
}
};
int
main() {
fixed_array<int,5> ary1;
fixed_2d_array<int> ary2;
ary2 = ary1;
ary1 = ary2;
ary2 += ary1;
ary1 += ary2;
return 0;
}