Специализация шаблонов классов с общей функциональностью
Я пишу простую математическую библиотеку с типом вектора шаблона:
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;
}