简体   繁体   English

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

[英]Class template specializations with shared functionality

I'm writing a simple maths library with a template vector type: 我正在编写一个带有模板矢量类型的简单数学库:

template<typename T, size_t N>
class Vector {
    public:
        Vector<T, N> &operator+=(Vector<T, N> const &other);
        // ... more operators, functions ...
};

Now I want some additional functionality specifically for some of these. 现在我想要一些专门用于其中一些功能的附加功能。 Let's say I want functions x() and y() on Vector<T, 2> to access particular coordinates. 假设我想要Vector<T, 2>上的函数x()y()来访问特定的坐标。 I could create a partial specialization for this: 我可以为此创建一个部分特化:

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

But now I'm repeating everything that already existed in the generic template. 但现在我正在重复通用模板中已存在的所有内容。

I could also use inheritance. 我也可以使用继承。 Renaming the generic template to VectorBase , I could do this: 将通用模板重命名为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;
};

However, now the problem is that all operators are defined on VectorBase , so they return VectorBase instances. 但是,现在问题是所有运算符都在VectorBase上定义,因此它们返回VectorBase实例。 These cannot be assigned to Vector variables: 这些不能分配给Vector变量:

Vector<float, 3> v;
Vector<float, 3> w;
w = 5 * v; // error: no conversion from VectorBase<float, 3> to Vector<float, 3>

I could give Vector an implicit conversion constructor to make this possible: 我可以给Vector一个隐式的转换构造函数来实现这个:

template<typename T, size_t N>
class Vector : public VectorBase<T, N> {
    public:
        Vector(VectorBase<T, N> const &other);
};

However, now I'm converting from Vector to VectorBase and back again. 但是,现在我将从Vector转换为VectorBase并再次返回。 Even though the types are the same in memory, and the compiler might optimize all this away, it feels clunky and I don't really like to have potential run-time overhead for what is essentially a compile-time problem. 尽管类型在内存中是相同的,并且编译器可能会优化所有这些,但它感觉很笨,我真的不喜欢潜在的运行时开销,这本质上是一个编译时问题。

Is there any other way to solve this? 有没有其他方法可以解决这个问题?

I think you can use CRTP to solve this problem. 我认为你可以使用CRTP来解决这个问题。 This idiom is used in boost::operator . 这个习惯用法在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 !!
}

Here's something I came up with when playing with C++0x features a while back. 这是我在使用C ++ 0x功能时想出来的东西。 The only C++0x feature used in this is static_assert , so you could use Boost to replace that. 这里使用的唯一C ++ 0x特性是static_assert ,因此您可以使用Boost来替换它。

Basically, we can use a static size check function that just checks to be sure a given index is less than the size of the vector. 基本上,我们可以使用静态大小检查函数,只检查以确定给定的索引小于向量的大小。 We use a static assert to generate a compiler error if the index is out of bounds: 如果索引超出范围,我们使用静态断言来生成编译器错误:

template <std::size_t Index> 
void size_check_lt() const 
{ 
    static_assert(Index < N, "the index is not within the range of the vector"); 
}

Then we can provide a get() method that returns a reference to the element at a given index (obviously a const overload would be useful too): 然后我们可以提供一个get()方法,它返回给定索引处元素的引用(显然const过载也很有用):

template <std::size_t Index> 
T& get()
{ 
    size_check_lt<Index>(); return data_[Index]; 
}

Then we can write simple accessors like so: 然后我们可以像这样编写简单的访问器:

T& x() { return get<0>(); }
T& y() { return get<1>(); }
T& z() { return get<2>(); }

If the vector has only two elements, you can use x and y but not z. 如果向量只有两个元素,则可以使用x和y但不能使用z。 If the vector has three or more elements you can use all three. 如果向量具有三个或更多元素,则可以使用所有三个元素。

I ended up doing the same thing for constructors--I created constructors for vectors of dimension two, three, and four and added a size_check_eq that allowed them to be instantiated only for vectors of dimension two, three, and four, respectively. 我最终为构造函数做了同样的事情 - 我为二维,三维和四维的向量创建了构造函数,并添加了一个size_check_eq ,允许它们分别仅对二维,三维和四维的向量进行实例化。 I can try and post the complete code when I get home tonight, if anyone is interested. 如果有人感兴趣的话,我今晚回家时可以尝试发布完整的代码。

I dropped the project halfway through, so there might be some huge problem with doing it this way that I didn't run into... at least it's an option to consider. 我把项目放到了一半,所以这样做可能会有一些很大的问题,我没有遇到过......至少它是一个可以考虑的选择。

The simplest way ? 最简单的方法? Using external functions: 使用外部功能:

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

In template programming using external functions is the simplest way to add functionality, simply because of the specialization issue you just encountered. 在模板编程中使用外部函数是添加功能的最简单方法,因为您刚刚遇到的特殊问题。

On the other hand, you could still provide x , y and z for any N or perhaps use enable_if / disable_if features to restrict the scope. 另一方面,您仍然可以为任何N提供xyz ,或者使用enable_if / disable_if功能来限制范围。

I don't know if you can get around the typing problems with the assignment operator, but you can make life a little easier by defining template versions of the various operators, helper functions to implement them, and then use inheritance. 我不知道你是否可以解决赋值运算符的输入问题,但是你可以通过定义各种运算符的模板版本,实现它们的辅助函数,然后使用继承来使生活变得更容易。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM