Composable Templated Vectors

In this blog entry, I present a code snippet demonstrating how a templated math vector class can have a construction interface which allows it to be composed of any number of smaller or equal-dimension vector types. It’s only requirement is that the value type is convertible to it’s own value type.

Over the last while I have been doing a fair bit in OpenCL at work. It has been quite an interesting experience in discovering where performance bottlenecks lie when coding for the GPU.

However, all that aside, there was one feature in OpenCL which I found quite handy: how composable the vector types are.

One can write code like this:

float3 vector = (float3)(1,0,0); // regular init
float4 point = (float4)( vector, 1 ); // init from a vector3 and additional component
float4 funky = (float4)( point.xyz, 5 ); // grab xyz of a vector4 and add a component

It’s quite handy and for a brief moment I considered all the C-style math libraries I have used during my time writing C++ code in the video games industry and lamented that I didn’t have anything this handy for C++. So I figured why not try implement it and see how it comes out.

To get great composability, it’s really just a variadic template for one constructor. It would need to use SFINAE to restrict its applicability so it doesn’t consume any parameter types which the vector clearly cannot be composed with, and allow other constructors to be able to handle those cases.

Furthermore there is room to report error conditions with static_assert (such as not supplying enough parameters).

The use-case isn’t quite the same as the OpenCL but similar enough:

typedef vec<float, 3> vec3f;
typedef vec<float, 4> vec4f;
typedef vec<int, 2> vec2i;
vec3f v3 = vec3f(1,2,3); // variadic template accepts int's as they are convertible to float
vec4f v4 = vec4f(v3, 1.0f); // accepts any permutation of convertible vectors and values which would total 4 dimensions
vec4f v4_2 = vec4f( vec2i, vec2i ); // even this - where would one use this anyway?
vec4f v4_3 = v4.swizzle<3,2,1,0>(); // reverse the values returning a vec4f
vec4f v4_4 = vec2i.swizzle<0,1,0,0>(); // even this

Now Visual Studio 2013 has a few bugs in it. It seems to be a little fragile when it comes to variadic template expansion. I had a few internal compiler errors where GCC was quite happy with the code. So some of the code is more verbose than it needs to be. Were VS2013 as compliant as GCC is, I wouldn’t need a ‘detail’ namespace as the code therein would be located inside the vector class itself, and it would be more succinct too.

Thus the code is more verbose and looks a bit more messy than it ideally would be.

In a rather naive test I write some code using a typical C style struct for a vector, did some operations and then did similar operations with this class. For a release build with Visual Studio the assembly generated was pretty much the same (aside from a difference in choice of registers) so it at least indicates this should (as expected) be similar in performance.

Of course proper testing would be needed.

So heres the code for it – just the shell to demonstrate the aforementioned variadic constructor and swizzle functionality.

#include <type_traits>
#include <utility>

template< class T, int DIM >
class vec;

namespace detail
{
	// whether given type is a vec<T,DIM> whose T can be converted to our T
	// does not check whether the DIM is compatible
	template< typename VECTOR_TYPE, class THIS_TYPE >
	struct is_convertible_vec
	{
	private:
		struct yes {};
		template< class OTHER_T, int OTHER_DIM >
		static typename std::enable_if< std::is_convertible< OTHER_T, typename VECTOR_TYPE::value_type >::value, yes >::type Check(const vec<OTHER_T, OTHER_DIM>& p);
		static void Check(...);
	public:
		const static bool value = std::is_same< decltype(Check(std::declval<THIS_TYPE>())), yes >::value;
	};

	// whether THIS_TYPE is an acceptable construction or set parameter for this vec
	template< typename VECTOR_TYPE, typename THIS_TYPE >
	struct is_acceptable_parameter
	{
		const static bool value = std::is_convertible<THIS_TYPE, typename VECTOR_TYPE::value_type>::value || is_convertible_vec<VECTOR_TYPE, THIS_TYPE>::value;
	};

	// VS2013 is a little flakey with its variadic templates. 
	// A more succinct is_acceptable_parameter<Ts>... expansion causes issues. 
	template< typename VECTOR_TYPE, typename... Ts >
	struct vec_acceptable_parameters;

	template< typename VECTOR_TYPE >
	struct vec_acceptable_parameters < VECTOR_TYPE >
	{
		const static bool value = true;
	};

	template< typename VECTOR_TYPE, typename THIS_TYPE, typename... Ts >
	struct vec_acceptable_parameters < VECTOR_TYPE, THIS_TYPE, Ts... >
	{
		const static bool value = is_acceptable_parameter< VECTOR_TYPE, THIS_TYPE >::value && vec_acceptable_parameters< VECTOR_TYPE, Ts... >::value;
	};
}

template< class T, int DIM >
class vec
{
public:
	typedef T value_type;
	typedef T& reference_type;
	typedef vec<T, DIM> this_type;
	const static int dimensions = DIM;

private:
	template< int INDEX, typename... Ts >
	void set(void)
	{
		static_assert(INDEX == DIM || INDEX == 0, "Number of construction items does not match the vector dimensions");
	}

	// accept values which are convertible to T
	template< int INDEX, typename THIS_TYPE, typename... Ts >
	typename std::enable_if< std::is_convertible<THIS_TYPE, T>::value >::type set(THIS_TYPE p, Ts... params)
	{ 
		static_assert(INDEX < DIM, "Out of bounds set on vector");
		v[INDEX] = p;
		set< INDEX+1, Ts... >(params...);
	}

	// accept other vectors which are convertible to T
	template< int INDEX, typename THIS_TYPE, typename... Ts >
	typename std::enable_if< detail::is_convertible_vec<this_type, THIS_TYPE>::value >::type set(THIS_TYPE p, Ts... params)
	{
		static_assert((INDEX + THIS_TYPE::dimensions) <= DIM, "Out of bounds set on vector");
		//std::copy(p.v, &p.v[THIS_TYPE::dimensions], &v[INDEX]);
		for (int i = 0; i < THIS_TYPE::dimensions; ++i) v[INDEX + i] = p.v[i];
		set< INDEX + THIS_TYPE::dimensions, Ts... >(params...);
	}
		
public:

	vec() = default;

	template< typename THIS_TYPE, typename... Ts,
		typename std::enable_if< detail::vec_acceptable_parameters<this_type, Ts...>::value >::type* = nullptr >
		vec(THIS_TYPE first, Ts... params)
	{
		set<0>(first, params...);
	}

	reference_type operator[](int i) { return v[i]; }
	const reference_type operator[](int i) const { return v[i]; }
	
	template< int... INDECES >
	auto swizzle() const -> vec< value_type, sizeof...(INDECES) > { return vec< value_type, sizeof...(INDECES) >(v[INDECES]...); }

	value_type v[DIM];
};
Advertisements
Composable Templated Vectors

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s