4

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?

EDIT:

Sorry, yes I have virtual dtor, but I forgot to paste it, the code doesn't compile without it.

Panayiotis Karabassis
  • 2,278
  • 3
  • 25
  • 40
  • Basically operators should return objects with the same type as class. – Blood Jul 02 '12 at 21:09
  • 3
    Off the top of my head, I'd think that CRTP could make this much cleaner. Also, the operators can be nonmember functions, so you could make them nonmember function templates, instantiated for each `Vector`. – James McNellis Jul 02 '12 at 21:11
  • @ildjarn: I think you get undefined behavior there, not a well-defined null. – Ben Voigt Jul 02 '12 at 21:13
  • 1
    Also, why would you want to do *dynamic_cast * const>(this) instead of just dynamic_cast&>(*this)? Besides being simpler and saying what it does more directly, if the dynamic_cast fails, wouldn't you rather throw an exception than try to dereference NULL? – abarnert Jul 02 '12 at 21:13
  • @JamesMcNellis: Obviously `this` can't be null (assuming no prior invocation of undefined behavior), but the `dynamic_cast` is allowed when the pointer contains a null pointer value, so I don't think it can be rejected at compile time. – Ben Voigt Jul 02 '12 at 21:16
  • C++ wears implementer glasses. You make a class abstract by declaring a method = 0. Like the destructor. – Hans Passant Jul 02 '12 at 21:38
  • 1
    @James: Would you make that an answer? It's much better than any of the existing pseudo-solutions. – Ben Voigt Jul 02 '12 at 21:39

5 Answers5

5

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.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • Thanks, this seems to be the most concrete answer. However I would like to note that CRTP does not prevent errors if the user misuses the DerivedVector type parameter. For example consider the otherwise unrelated: `class Bla : BaseVector` and `class Alb : BaseVector`. Calling the Alb's `operator+=` will try to cast an Alb instance (though BaseVector) into a Bla instance. If this is a static cast, it won't be caught at compile time I think, since Bla is a subclass of BaseVector. Correct me if I am wrong. – Panayiotis Karabassis Jul 02 '12 at 22:30
  • 2
    You are correct. The burden is still on the derived class to provide the correct template parameter. You can provide some verification, e.g., by making all base class constructors protected and having each take a `DerivedVector*` as the parameter. The derived class then must pass `this`. Of course, an evil derived class could pass nullptr or another `DerivedVector*`, but one cannot protect against people determined to break things. – James McNellis Jul 02 '12 at 22:33
4

Yes, from a technical point of view it is OK. However, in order for the dynamic_cast to work your base class needs to be polymorphic. So you need to make at least the destructor (pure) virtual.

I also want to add that instead of:

// potential null dereference
return *dynamic_cast<Vector<T> * const>(this);

it is safer to write:

// potential std::bad_cast exception
return dynamic_cast<Vector<T> & const>(*this);

To answer your original question:

Is there a better way to achieve what I am trying to do (avoid code duplication, and unnecessary casts in the user code)?

Yes. Read about static polymorphism, curiously recurring template pattern and policy-based class design if you want to learn more.

StackedCrooked
  • 34,653
  • 44
  • 154
  • 278
  • Dynamic casting the `this' pointer is not undefined behavior. – StackedCrooked Jul 02 '12 at 21:14
  • A downcast from a non-polymorphic type violates the requirement in 5.2.6p6. It is not well-defined. – Ben Voigt Jul 02 '12 at 21:15
  • That's probably right. But in my experience dynamic_cast on a non-polymorphic object gets me a compiler error. If the compiler does not give an error, then yeah, I guess it's UB. – StackedCrooked Jul 02 '12 at 21:18
  • I think he was saying that the it's OK, EXCEPT for the class not being polymorphic. – Panayiotis Karabassis Jul 02 '12 at 21:18
  • @StackedCrooked: The way I read the standard, it needs to work if the pointer has null pointer value (a runtime check), so except for very specific cases (including this one), it can't diagnose at compile time. – Ben Voigt Jul 02 '12 at 21:19
3

None of your methods are virtual. Without any virtual methods, the compiler won't add any run-time type information. Without RTTI, dynamic_cast will not work.

1

I think a better way to accomplish this is the pimpl idiom. As you say, BaseVector is just an implementation detail. As such, clients of your class should have no knowledge of it (which leaves you free to change it).

In this case, this would entail having Vector contain a BaseVector rather than inherit from it. It would then define its own arithmetic assignment operators, which would forward to BaseVector.

This does not violate DRY, because there is still only one version of the implementation details, and they reside in BaseVector. Repeating the interface is perfectly fine.

JohnMcG
  • 8,709
  • 6
  • 42
  • 49
  • The whole point of the base class appears to be to avoid the duplicate forwarders. – Ben Voigt Jul 02 '12 at 21:19
  • IMO, exposing implementation details is a greater sin than duplicate forwarders. – JohnMcG Jul 02 '12 at 21:21
  • Here, the implementation details affect the layout of the type, as `BaseVector` is derived from `array`, which has as a data member a `T[N]`. Using a pimpl would require dynamic allocation, which for a three-dimensional vector would likely be harmful to performance. – James McNellis Jul 02 '12 at 22:04
0

This is an old question, but I recently had to solve this same problem and then happened to stumble across this, so I thought I'd answer.

I used a type wrapper in order to have the specialisation 'derive from itself':

template <class T>
struct type_wrapper;

template <class T>
struct unwrap_type
{
    typedef T type;
};

template <class T>
struct unwrap_type<type_wrapper<T>>
{
    typedef T type;
};

template <class WrappedT>
class Vector: public boost::array<typename unwrap_type<WrappedT>::type, 3>
{
    typedef typename unwrap_type<WrappedT>::type T;
protected:
    Vector(const T x, const T y, const T z);
public:
    bool operator == (const Vector &other) const;
    Vector<T> operator + (const Vector &other) const;    
    Vector<T> operator - (const Vector &other) const;
    Vector<T> &operator += (const Vector &other)
    {
        (*this)[0] += other[0];
        (*this)[1] += other[1];
        (*this)[2] += other[2];

        return static_cast<Vector<T> &>(*this);
    }
}

template <>
class Vector<double> : public Vector<type_wrapper<double>>
{
public:
    Vector(const double x, const double y, const double z);
    Vector(const Vector<int> &other);
    double norm() const;
};

That way, you don't need any redundant classes - except for the type wrapper and metafunction, which are reusable.

Fuz
  • 235
  • 2
  • 4