Специализации шаблонов классов с общей функциональностью - PullRequest
15 голосов
/ 03 мая 2010

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

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 ]

9 голосов
/ 09 мая 2010

Я думаю, вы можете использовать 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 !!
}
4 голосов
/ 03 мая 2010

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

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

0 голосов
/ 03 мая 2010

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

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 голосов
/ 03 мая 2010

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

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 для ограничения области.

...