views:

632

answers:

4

Considering the following two usage scenarios (exactly as you see them, that is, the end-user will only be interested in using Vector2_t and Vector3_t):

[1]Inheritance:

template<typename T, size_t N> struct VectorBase
{
};

template<typename T> struct Vector2 : VectorBase<T, 2>
{
};

template<typename T> struct Vector3 : VectorBase<T, 3>
{
};

typedef Vector2<float> Vector2_t;
typedef Vector3<float> Vector3_t;

[2]Specialization:

template<typename T, size_t N> struct Vector
{
};

template<typename T> struct Vector<T, 2>
{
};

template<typename T> struct Vector<T, 3>
{
};

typedef Vector<float, 2> Vector2_t;
typedef Vector<float, 3> Vector3_t;

I can't make up my mind as to which is a nicer solution. The obvious advantage to inheritance is code reuse in the derived classes; a possible disadvantage being performance (bigger size, users may pass by value, etc). Specialization seems to avoid all that, but at the expense of me having to repeat myself multiple times.

What other advantages/disadvantages did I miss, and in your opinion, which route should I take?

A: 

If you're using template specialization too much, you probably need to rethink your design. Considering that you're hiding it behind a typedef, I doubt you need it.

eduffy
+2  A: 

Use inheritance and private inheritance. And don't use any virtual functions. Since with private inheritance, you don't have is-a, no one will be able to use a baseclas pointer to a derived subclass, and you won't get the slicing problem when passinfg by value.

This gives you the best of both worlds (and indeed it's how most libraries implement many of the STL classes).

From http://www.hackcraft.net/cpp/templateInheritance/ (discussing std::vector, not your Vector class):

vector<T*> is declared to have a private base of vector<void*>. All functions which place a new element into the vector, such as push_back(), call the equivalent function on this private base, so internally our vector<T*> is using a vector<void*> for storage. All functions which return an element from the vector, such as front(), perform a static_cast on the result of calling the equivalent function on the private base. Since the only way to get a pointer into the vector<void*> (apart from deliberately dangerous tricks) is through the interface offered by vector<T*> it is safe to staticly cast the void* back to T* (or the void*& back to T*&, and so on).

In general, if the STL does it like this, it seems like a decent model to emulate.

tpdi
That's a good point, I totally forgot about the STL.
+9  A: 

What you ultimately want, i think, is to have the user type

Vector<T, N>

And depending on N, the user will get slight different things. The first will not fulfill that, but the second will, on the price of code duplication.

What you can do is to invert the inheritance:

template<typename T, size_t N> struct VectorBase 
{
};

template<typename T> struct VectorBase<T, 2>
{
};

template<typename T> struct VectorBase<T, 3>
{
};

template<typename T, size_t N> struct Vector : VectorBase<T, N>
{
};

And implement the few functions that depend only on N being some specific value in the appropriate base-class. You may add a protected destructor into them, to prevent users deleting instances of Vector through pointers to VectorBase (normally they should not even be able to name VectorBase: Put those bases in some implementation namespace, like detail).

Another idea is to combine this solution with the one mentioned in another answer. Inherit privately (instead of publicly as above) and add wrapper functions into the derived class that call the implementations of the base-class.

Yet another idea is to use just one class and then enable_if (using boost::enable_if) to enable or disable them for particular values of N, or use a int-to-type transformer like this which is much simplier

struct anyi { };
template<size_t N> struct i2t : anyi { };

template<typename T, size_t N> struct Vector
{
    // forward to the "real" function
    void some_special_function() { some_special_function(i2t<N>()); }

private:
    // case for N == 2
    void some_special_function(i2t<2>) {
        ...
    }

    // case for N == 3
    void some_special_function(i2t<3>) {
        ...
    }

    // general case
    void some_special_function(anyi) {
        ...
    }
};

That way, it is completely transparent to the user of Vector. It also won't add any space overhead for compilers doing the empty base class optimization (quite common).

Johannes Schaub - litb
Most complete answer. Thank you!
Nice and COOL answer. Takes a guru to get such an elegant answer.
J.W.
+1. I suspect your suggestion of enable_if<> in a single class template is the best way -- surely the only differences between the template classes for different vector dimensions would be the number of parameters in the constructor?
j_random_hacker
yeah, enable_if would be totally "zero-cost" :) this now hopes to eliminate those i2t objects. although they should be easy to optimize away. a small expression template overloading op, could serve to provide the initializing elements to the ctor. or using op+= like boost::assign would either.
Johannes Schaub - litb
i thought maybe he wanted to add things like cross product that he only adds for some N like N==3, for which he could use this some_special_function(i2t<3>) or enable_if
Johannes Schaub - litb
@litb: I like the expression templates idea. :)
j_random_hacker
j_random_hacker, something like this i wrote some days ago: http://groups.google.com/group/comp.lang.c++/msg/b6da57ff38dc920a
Johannes Schaub - litb
A: 

Inheritance should only be used to model "is-a". Specialization would be the cleaner alternative. If you need or want to use inheritance for whatever reason, at the very least make it private or protected inheritance, so you don't inherit publicly from a class with a public non-virtual destructor.

Yes, the template metaprogramming guys always do

 struct something : something_else {};

But those somethings are metafunctions and not meant to be used as types.