代码之家  ›  专栏  ›  技术社区  ›  Thomas

具有共享功能的类模板专门化

  •  15
  • Thomas  · 技术社区  · 14 年前

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

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

    但是,现在的问题是所有操作符都是在 ,所以他们回来了 矢量基 实例。这些不能分配给 Vector 变量:

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

    隐式转换构造函数使这成为可能:

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

    然而,现在我从 矢量 又回来了。尽管这些类型在内存中是相同的,而且编译器可能会优化掉所有这些,但这感觉很笨拙,而且我真的不希望有潜在的运行时开销来解决本质上的编译时问题。

    4 回复  |  直到 14 年前
        1
  •  10
  •   Rexxar    14 年前

    我想你可以用 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 !!
    }
    
        2
  •  4
  •   James McNellis    14 年前

    这是我在玩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() 方法返回对给定索引处元素的引用(显然常量重载也很有用):

    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和4的向量进行实例化。如果有人感兴趣的话,我可以试着在今晚回家的时候把完整的代码贴出来。

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

        3
  •  0
  •   Matthieu M.    14 年前

    最简单的方法?使用外部函数:

    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 限制范围的功能。

        4
  •  0
  •   D.Shawley    14 年前

    我不知道您是否可以绕过赋值运算符的键入问题,但是您可以通过定义各种运算符的模板版本、实现它们的助手函数,然后使用继承来简化工作。

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