16

我正在编写一个带有模板向量类型的简单数学库:

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

但是,现在我又来回转换了VectorVectorBase尽管内存中的类型相同,并且编译器可能会优化所有这些,但感觉很笨重,而且我真的不喜欢对本质上是编译时问题的潜在运行时开销。

有没有其他方法可以解决这个问题?

4

4 回答 4

11

我认为你可以使用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 !!
}
于 2010-05-09T16:48:15.280 回答
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。如果向量有三个或更多元素,您可以使用所有三个。

我最终为构造函数做了同样的事情——我为二、三和四维向量创建了构造函数,并添加了一个size_check_eq允许它们分别只为二、三和四维向量实例化的构造函数。如果有人感兴趣,我可以在今晚回家时尝试发布完整的代码。

我在中途放弃了这个项目,所以这样做可能会有一些我没有遇到的大问题……至少这是一个可以考虑的选择。

于 2010-05-03T12:22:33.157 回答
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和来限制范围。zNenable_ifdisable_if

于 2010-05-03T12:23:34.357 回答
0

我不知道您是否可以解决赋值运算符的打字问题,但您可以通过定义各种运算符的模板版本、实现它们的辅助函数,然后使用继承,让生活变得更轻松一些。

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;
}
于 2010-05-03T12:44:22.157 回答