Limegarden.net Personal site of Wouter Lindenhof

7Apr/103

Solved undesired template specification

A while ago (why do I always start with that) I wrote an blog entry about undesired template specification, what to encounter and how to work around it.

Anyway here is a quick definition of two structures I will be using in the article:


/**
 * I will be using the following structures throughout the article
 */
template <typename T> struct Vector3<T> {
	union {
		struct { T x, y, z; }
		T array[3];
	};
	Vector3<T>() : x(0), y(0), z(0) {}
	Vector3<T>(T nx, T ny, T nz) : x(nx), y(ny), z(nz) {}
};

template <typename T> struct Vector4<T> {
	union {
		struct { T x, y, z, w; }
		T array[4];
	};
	Vector4<T>() : x(0), y(0), z(0), w(0) {}
	Vector3<T>(T nx, T ny, T nz, T nw) : x(nx), y(ny), z(nz), w(nw) {}
};
typedef Vector3<unsigned int> Vector3u;
typedef Vector3<float>        Vector3f;
typedef Vector4<unsigned int> Vector4u;
typedef Vector4<float>        Vector4f;

struct SVertex
{
	Vector4f pos;
	Vector3f normal;
	Vector2f texcoord;
};

Because of Brick (3D random dungeon generator that takes design into account) I have noticed that there is one thing I do failry often:


Vector3f position;
SVertex vertex;

/* Need to draw it, so I store position in vertex */
vertex.pos = position; // ERROR! Trying to assign Vector3f to Vector4f!!

And finally I used defines to do the conversion for me:


#define VEC3TOVEC4(v) Vector4f((v).x, (v).y, (v).z, 0)
#define VEC4TOVEC3(v) Vector3f((v).x, (v).y, (v).z)

/* New code becomes */
vertex.pos = VEC3TOVEC4(position); // Works

Of course the above code is not nice to look at and I find it even plain ugly, but it works. However I don't want to do that in future projects (it feels like a hack), I would need to define something like that for every type (float, double, unsigned int et cetera) and on top of that it generates warnings:


Vector3<double> position;	// Notice it is unsigned!
SVertex vertex;

/* Need to draw it, so I store position in vertex */
vertex.pos = VEC3TOVEC4(position); // Works, but generates warning about losing information

But I wouldn't be writing this post unless I tackled that little issue, and for once I can add that the solution is quite nice as well.


Vector3<double> position;	// Notice it is unsigned!
SVertex vertex;

/* Need to draw it, so I store position in vertex */
vertex.pos.Set(position.array, 3); 		// Works, no errors and no warnings
vertex.pos = Vector4f(position.array, 3);	// Works fine as well

So what did I change?

Well, I used mutliple template (one for the class and for the function/constructor). This looks something like this:


template <typename T> struct Vector4
{
	/* ... */
	template <typename R>
	explicit inline Vector4<T>(const R* values, const unsigned int elements/*=4*/);
	template <typename R>
	inline Vector4<T>& Set(const R* values, const unsigned int elements/*=4*/);
	/* ... */
};

// Implementation
template <typename T> template <typename R>
Vector4<T>::Vector4(const R* values, unsigned int elements)
	: x(elements > 0 ? (T)values[0] : 0), y(elements > 1 ? (T)values[1] : 0)
	, z(elements > 2 ? (T)values[2] : 0), w(elements > 3 ? (T)values[3] : 0)
{
}
template <typename T> template <typename R>
Vector4<T>& Vector4<T>::Set(const R* values, const unsigned int elements)
{
	x = (elements > 0 ? (T)values[0] : 0);
	y = (elements > 1 ? (T)values[1] : 0);
	z = (elements > 2 ? (T)values[2] : 0);
	w = (elements > 3 ? (T)values[3] : 0);
	return *this;
}

If you take a look at the code I think it is quite clear except that you might have some questions.

  1. Q: Why do you use [cci_cpp]explicit[/cci_cpp] with the constructor?
    A: Because that prevents the implicit use of constructors. If I would allow it a [cci_cpp]Vector4u[/cci_cpp] could be implicit assigned to [cci_cpp]Vector3f[/cci_cpp], although it would be missing an argument. However I think that when you are converting, you should be somewhat aware of it, especially when it can be expensive.
  2. Q: Why have you commented out the default value for [cci_cpp]elements[/cci_cpp]?
    A: Because you don't know how many elements there are in [cci_cpp]values[/cci_cpp] might be (there is a method to find out).
  3. Q: So why don't you find out automatically and what is with those conditionals in the constructor?
    A: Those two are related. By telling it explicitly there is a real good chance that the compiler removes the conditionals, so the [cci_cpp] ( check ? true-value : false-value) [/cci_cpp] check will be removed.
Comments (3) Trackbacks (0)
  1. Q: Why don’t you define a Vector4 ctor? like that:

    template struct Vector4
    {
    template explicit Vector4(const Vector3& v, float w = 1) { … } /* or without default value if you like */
    };

  2. Uh, blog software ate all my braces. Trying again:

    Q: Why don’t you define a Vector4 ctor? like that:

    template struct Vector4
    {
    template
    explicit Vector4(const Vector3& v, float w = 1) { ... } /* or without default value if you like */
    };

  3. Arseny Kapoulkine :

    Uh, blog software ate all my braces. Trying again:

    Q: Why don’t you define a Vector4 ctor? like that:

    1
    2
    3
    4
    template <typename T> struct Vector4
    {
        template <typename U> explicit Vector4(const Vector3<U>& v, float w = 1) { … } /* or without default value if you like */
    };

    I tried that but I prefer the above for multiple reasons:
    1. It means that I need to create a constructor that does Vector3 and Vector2, which means that Vector3 and Vector4 need to be defined (but not declared) at that point.
    2. Not all data that is delivered is my custom Vector class, sometimes (when using an external library for physics for example) you have a different type.
    3. In general it’s less work ;)

    Of course, the last reason was the most important reason. The results are virtually the same, although yours look nicer, but those can always be added at a later stage.


Leave a comment

(required)

No trackbacks yet.