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

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

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

Есть ли другой способ решить эту проблему?

Ответы

Ответ 1

Я думаю, вы можете использовать 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

Здесь кое-что, что я придумал, когда вы играете с функциями С++ 0x некоторое время назад. Единственная функция С++ 0x, используемая в этом, - static_assert, поэтому вы можете использовать Boost для ее замены.

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

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. Если у вектора есть три или более элемента, вы можете использовать все три.

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

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

Ответ 3

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

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

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

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