This is more of a design question.
I have a template class, and I want to add extra methods to it depending on the template type. To practice the DRY principle, I have come up with this pattern (definitions intentionally omitted):
template <class T>
class BaseVector: public boost::array<T, 3>
{
protected:
BaseVector<T>(const T x, const T y, const T z);
public:
bool operator == (const Vector<T> &other) const;
Vector<T> operator + (const Vector<T> &other) const;
Vector<T> operator - (const Vector<T> &other) const;
Vector<T> &operator += (const Vector<T> &other)
{
(*this)[0] += other[0];
(*this)[1] += other[1];
(*this)[2] += other[2];
return *dynamic_cast<Vector<T> * const>(this);
}
virtual ~BaseVector<T>()
{
}
}
template <class T>
class Vector : public BaseVector<T>
{
public:
Vector<T>(const T x, const T y, const T z)
: BaseVector<T>(x, y, z)
{
}
};
template <>
class Vector<double> : public BaseVector<double>
{
public:
Vector<double>(const double x, const double y, const double z);
Vector<double>(const Vector<int> &other);
double norm() const;
};
I intend BaseVector to be nothing more than an implementation detail. This works, but I am concerned about operator+=
. My question is: is the dynamic cast of the this
pointer a code smell? Is there a better way to achieve what I am trying to do (avoid code duplication, and unnecessary casts in the user code)? Or am I safe since, the BaseVector constructor is private?
Sorry, yes I have virtual dtor, but I forgot to paste it, the code doesn't compile without it.
I would recommend that you consider an alternative approach (note that in the below examples, I have simplified the code to the bare minimum required to demonstrate the alternative approaches).
First, consider the Curiously Recurring Template Parameter (CRTP):
template <typename T, typename DerivedVector>
struct BaseVector
{
DerivedVector& operator+=(DerivedVector const& other)
{
return static_cast<DerivedVector&>(*this);
}
};
template <typename T>
struct Vector : BaseVector<T, Vector<T>>
{
};
Since you always know what the derived type is, a static_cast
is sufficient. If Vector<T>
is the only class whose base is BaseVector<T>
and if you are guaranteed that the T
parameters are always the same, then strictly speaking, the CRTP parameter is unnecessary: you always know what the derived type is, so a static_cast
is safe.
Alternatively, consider that operators need not be member functions, so you could declare non-member operator function templates:
template <typename T, typename DerivedVector>
struct BaseVector
{
};
template <typename T>
struct Vector : BaseVector<T, Vector<T>>
{
};
template <typename T>
Vector<T>& operator+=(Vector<T>& self, Vector<T> const& other)
{
return self;
}
While the latter is preferable for operators, it won't work for ordinary, nonoperator member functions, so the CRTP approach would be preferable for those.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With