I am going to answer my own question after playing around for some time. I think I have found what I am looking for although this is just a rough sketch. I will post this, in case others are curious about similar problems.
Definition: Tuple.hpp
#include <cassert>
#include <iostream>
namespace details
{
template<bool>
struct rule_is_greater_than_4;
template<>
struct rule_is_greater_than_4<true> {};
template<>
struct rule_is_greater_than_4<false> {};
template<class T, size_t N, size_t M>
class inner_storage : rule_is_greater_than_4< ( M > 4 )>
{
public:
T x, y, z, w;
private:
T more_data[ N - 4 ];
};
template<class T, size_t N>
class inner_storage<T, 2, N>
{
public:
T x, y;
};
template<class T, size_t N>
class inner_storage<T, 3, N>
{
public:
T x, y, z;
};
template<class T, size_t N>
class inner_storage<T, 4, N>
{
public:
T x, y, z, w;
};
}
template<class T, size_t N>
class Tuple : public details::inner_storage<T, N, N>
{
public:
static_assert( N > 1, "Size of 'n-tuple' must be > 1." );
// -- Constructors --
Tuple();
Tuple( T k );
Tuple( T x, T y );
Tuple( T x, T y, T z );
Tuple( T x, T y, T z, T w );
// -- Access operators --
const size_t size();
T &operator[]( const size_t i );
T const &operator[]( const size_t i ) const;
// -- Unary arithmetic operators --
Tuple<T, N> &operator=( Tuple<T, N> const &t );
friend std::ostream &operator<<( std::ostream &os, Tuple<T, N> &t );
};
// -- Unary operators --
template<class T, size_t N>
Tuple<T, N> operator+( Tuple<T, N> const &t );
template<class T, size_t N>
Tuple<T, N> operator-( Tuple<T, N> const &t );
// -- Binary arithmetic operators --
template<class T, size_t N>
Tuple<T, N> operator+( Tuple<T, N> const &t1, Tuple<T, N> const &t2 );
template<class T, size_t N>
Tuple<T, N> operator-( Tuple<T, N> const &t1, Tuple<T, N> const &t2 );
template<class T, size_t N>
Tuple<T, N> operator*( T const &s, Tuple<T, N> const &t2 );
template<class T, size_t N>
Tuple<T, N> operator*( Tuple<T, N> const &t1, T const &s );
template<class T, size_t N>
Tuple<T, N> operator/( Tuple<T, N> &t1, T const &s );
// -- Boolean operators --
template<class T, size_t N>
bool operator==( Tuple<T, N> const &t1, Tuple<T, N> const &t2 );
template<class T, size_t N>
bool operator!=( Tuple<T, N> const &t1, Tuple<T, N> const &t2 );
// -- Stream operator --
template<class T, size_t N>
inline std::ostream &operator<<( std::ostream &os, Tuple<T, N> const &t );
#include "Tuple.inl"
Implementation: Tuple.inl
// -- Constructors --
template <class T, size_t N>
Tuple<T, N>::Tuple()
{
}
template <class T, size_t N>
Tuple<T, N>::Tuple( T k )
{
for( size_t i = 0; i < N; i++ )
{
operator[]( i ) = k;
}
}
template <class T, size_t N>
Tuple<T, N>::Tuple( T x, T y )
{
static_assert( N == 2, "This constructor is resererved for 2-tuples." );
this->x = x;
this->y = y;
}
template <class T, size_t N>
Tuple<T, N>::Tuple( T x, T y, T z )
{
static_assert( N == 3, "This constructor is resererved for 3-tuples." );
this->x = x;
this->y = y;
this->z = z;
}
template <class T, size_t N>
Tuple<T, N>::Tuple( T x, T y, T z, T w )
{
static_assert( N == 4, "This constructor is resererved for 4-tuples." );
this->x = x;
this->y = y;
this->z = z;
this->w = w;
}
// -- Access operators --
template <class T, size_t N>
const size_t Tuple<T, N>::size()
{
return N;
}
template <class T, size_t N>
T &Tuple<T, N>::operator[]( const size_t i )
{
assert( i < N );
return ( &( this->x ) )[ i ];
}
template <class T, size_t N>
T const &Tuple<T, N>::operator[]( const size_t i ) const
{
assert( i < N );
return ( &( this->x ) )[ i ];
}
// -- Unary arithmetic operators --
template<class T, size_t N>
Tuple<T, N> &Tuple<T, N>::operator=( Tuple<T, N> const &t )
{
for( size_t i = 0; i < size(); i++ )
{
this->operator[]( i ) = t[ i ];
}
return *this;
}
// -- Unary operators --
template<class T, size_t N>
Tuple<T, N> operator+( Tuple<T, N> const &t )
{
return t;
}
template<class T, size_t N>
Tuple<T, N> operator-( Tuple<T, N> const &t )
{
return t * T( 0.0f );
}
// -- Binary operators --
template<class T, size_t N>
Tuple<T, N> operator+( Tuple<T, N> const &t1, Tuple<T, N> const &t2 )
{
Tuple<T, N> sum;
for( size_t i = 0; i < N; i++ )
{
sum[ i ] = t1[ i ] + t2[ i ];
}
return sum;
}
template<class T, size_t N>
Tuple<T, N> operator-( Tuple<T, N> const &t1, Tuple<T, N> const &t2 )
{
Tuple<T, N> difference;
for( size_t i = 0; i < N; i++ )
{
difference[ i ] = t1[ i ] - t2[ i ];
}
return difference;
}
template<class T, size_t N>
Tuple<T, N> operator*( Tuple<T, N> const &t, T const &s )
{
Tuple<T, N> product;
for( size_t i = 0; i < N; i++ )
{
product[ i ] = t[ i ] * s;
}
return product;
}
template<class T, size_t N>
Tuple<T, N> operator*( T const &s, Tuple<T, N> const &t )
{
Tuple<T, N> product;
for( size_t i = 0; i < N; i++ )
{
product[ i ] = s * t[ i ];
}
return product;
}
template<class T, size_t N>
Tuple<T, N> operator/( Tuple<T, N> const &t, T const &s )
{
assert( s != T( 0.0f ) );
Tuple<T, N> quotient;
T denom = T( 1.0f ) / s;
for( size_t i = 0; i < N; i++ )
{
quotient[ i ] = t[ i ] * denom;
}
return quotient;
}
// -- Boolean operators --
template<class T, size_t N>
bool operator==( Tuple<T, N> const &t1, Tuple<T, N> const &t2 )
{
bool equal = true;
for( size_t i = 0; i < N; i++ )
{
equal = ( t1[ i ] == t2[ i ] ) ? equal : false;
}
return equal;
}
template<class T, size_t N>
bool operator!=( Tuple<T, N> const &t1, Tuple<T, N> const &t2 )
{
return !( t1 == t2 );
}
// -- Stream operator --
template <class T, size_t N>
inline std::ostream &operator<<( std::ostream &os, Tuple<T, N> const &t )
{
os << "( ";
for( size_t i = 0; i < t.size(); i++ )
{
os << t[ i ];
if( i < t.size() - 1 )
{
os << ", ";
}
}
os << " )";
return os;
}
I'm not sure if all the operator overloads are covered, but I wanted to be brief. I'm also not happy with the way the constructors are created (maybe variadic templates could come to the rescue). At least it gives me following desirable functionality:
typedef Tuple<float, 2> Vec2;
typedef Tuple<float, 4> Vec4;
typedef Tuple<float, 10> Vec10;
Vec2 a( 1.0 );
a.x = 3.0f;
a.y = -3.0f;
assert( a[ 1 ] == -3.0f ); // this works
// a.z = 1.0f; ------> compiler error
Vec10 b;
b.x = 0.0f; b.y = 1.0f; b.z = 2.0f; b.w = 3.0f;
b[ 5 ] = 12.0f;
// etc...