Специализации шаблона класса с общей функциональностью

Я пишу простую библиотеку математики с шаблонным типом вектора:

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 и назад снова. Даже при том, что типы являются тем же в памяти, и компилятор мог бы оптимизировать все это далеко, это чувствует себя неуклюжим, и мне действительно не нравится иметь потенциальное время выполнения наверху для того, что является по существу проблемой времени компиляции.

Там какой-либо другой путь состоит в том, чтобы решить это?

15
задан Thomas 3 May 2010 в 12:08
поделиться

4 ответа

Я думаю, что вы можете использовать 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 !!
}
9
ответ дан 1 December 2019 в 04:40
поделиться

Я не знаю, можно ли обойти проблемы типизации с оператором присваивания, но вы можете немного облегчить себе жизнь, определив шаблонные версии различных операторов, вспомогательные функции для их реализации, а затем использовать наследование.

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;
}
0
ответ дан 1 December 2019 в 04:40
поделиться

Вот кое-что, что я придумал, когда некоторое время назад играл с функциями 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 , который позволял создавать их экземпляры только для векторов размерности два, три и четыре соответственно. Я могу попробовать опубликовать полный код, когда вернусь домой сегодня вечером, если кому-то интересно.

Я отказался от проекта на полпути, поэтому может возникнуть огромная проблема, с которой я не столкнулся ... по крайней мере, это вариант, который стоит рассмотреть.

4
ответ дан 1 December 2019 в 04:40
поделиться

Самый простой способ? Использование внешних функций:

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 функции для ограничения области действия.

0
ответ дан 1 December 2019 в 04:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: