具有共享功能的类模板特化

时间:2010-05-03 11:55:47

标签: c++ templates template-specialization

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

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并再次返回。尽管类型在内存中是相同的,并且编译器可能会优化所有这些,但它感觉很笨,我真的不喜欢潜在的运行时开销,这本质上是一个编译时问题。

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

4 个答案:

答案 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提供yzN,或者使用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;
}