I'm in the process of creating a vector class and am trying to figure out ways to reuse the maximum amount of code for different size vectors.
Here's a basic example:
template<typename T, unsigned int D>
class Vector
{
public:
union {
T v[D];
struct {
/* T x;
* T y;
* T z;
* T w;
*/
};
};
Vector()
{
for(unsigned int i=0; i<D; ++i)
(*this)[i] = T(0);
}
Vector(T scalar)
{
for(unsigned int i=0; i<D; ++i)
(*this)[i] = scalar;
}
inline T operator[](int i) { return (*this).v[i]; }
};
I want the member variables to be publicly accessible. Ex:
Vector<float,2> vec;
printf("X: %.2f, Y: %.2f\n", vec.x, vec.y);
What I'd like to do is something along the lines of this:
template<typename T>
class Vector2 : public Vector<T,2, struct { T x; T y; }> {};
template<typename T>
class Vector3 : public Vector<T,2, struct { T x; T y; T z; }> {};
and have it override a struct in the union:
template<typename T, unsigned int D, struct C>
class Vector
{
public:
union {
T v[D];
// Place the passed struct here
};
};
Is there any feasible way to do this? I do not want to use anything other than the standard library if possible. Thanks in advance.
EDIT: After reading upon all of the answers, I have understood that the way I am using unions is incorrect! Thank you to #M.M for pointing this out. I have since chosen to go a different route, but I have selected the answer that best fit what I was looking for at the time. Once again, thank you for all of the welcomed responses below!
What you are trying to do is not allowed.
Anyway, you can do this:
template<typename T>
struct S { T x; T y; };
template<typename T>
class Vector2 : public Vector<T,2,S<T>> {};
Or this:
template<typename T>
class Vector2 : public Vector<T,2,S> {};
In the second case, Vector can be defined as:
template<typename T, unsigned int D, template<typename> class S>
class Vector {
using MyStruct = S<T>;
// ...
union {
T v[D];
MyStruct myStruct;
};
};
It doesn't scale very well to a large D, but if you're just after the four to six variants I'm imagining, you could partial-specialize a base class:
#include <iostream>
template<typename T, size_t D>
struct VectorBase;
template<typename T>
struct VectorBase<T, 2>
{
constexpr VectorBase() : v{} {}
union {
T v[2];
struct { T x, y; };
};
};
template<typename T>
struct VectorBase<T, 3>
{
constexpr VectorBase() : v{} {}
union {
T v[3];
struct { T x, y, z; };
};
};
template<typename T>
struct VectorBase<T, 4>
{
constexpr VectorBase() : v{} {}
union {
T v[4];
struct { T x, y, z, w; };
};
};
template<typename T, size_t D>
struct Vector : public VectorBase<T, D>
{
using VectorBase<T, D>::v;
using size_type = decltype(D);
using value_type = T;
constexpr Vector() : VectorBase<T,D>{} {}
constexpr Vector(T scalar) {
std::fill(std::begin(v), std::end(v), scalar);
}
constexpr T& operator[](size_type i) const noexcept { return v[i]; }
constexpr const T& operator[](size_type i) noexcept { return v[i]; }
constexpr size_type size() const noexcept { return D; }
constexpr T* data() noexcept { return &v[0]; }
constexpr const T* data() const noexcept { return &v[0]; }
};
template<typename T>
using Vector2 = Vector<T, 2>;
template<typename T>
using Vector3 = Vector<T, 3>;
template<typename T>
using Vector4 = Vector<T, 4>;
int main() {
Vector3<int> v{1};
std::cout << v[0] << ", " << v.z << "\n";
return 0;
}
Live demo: http://ideone.com/T3QHoq
If I understood you correctly your main purpose was to to declare order of fields that corresponds to the array elements of templated class. You cannot do this directly as templates does not accept inline type as parameter. To workaround the problem you could play with non-type template parameters to bind some labels to given index of an array:
#include <cstdio>
#include <unordered_map>
#include <utility>
struct Label { } x, y, z, w;
template <Label&... labels>
struct Pack { };
template <class, class>
struct VectorParent;
template <Label&... labels, size_t... Is>
struct VectorParent<Pack<labels...>, std::index_sequence<Is...>> {
static std::unordered_map<Label *, size_t> label_map;
};
template <Label&... labels, size_t... Is>
std::unordered_map<Label *, size_t> VectorParent<Pack<labels...>, std::index_sequence<Is...>>::label_map = {{&labels, Is}...};
struct LabelNotFound { };
template <class T, size_t N, Label&... labels>
struct Vector:VectorParent<Pack<labels...>, std::make_index_sequence<sizeof...(labels)>> {
static_assert(N == sizeof...(labels),
"the cound of labels should corespond to the number of elements of the vector");
using VectorParent<Pack<labels...>, std::make_index_sequence<sizeof...(labels)>>::label_map;
T t[N];
T &operator->*(Label& l) {
auto it = label_map.find(&l);
if (it == label_map.end())
throw LabelNotFound{};
return t[it->second];
}
};
int main() {
Vector<float,2,x,y> vec;
vec->*x = 10.0f;
printf("X: %.2f, Y: %.2f\n", vec->*x, vec->*y); // prints: X: 10.00, Y: 0.00
//vec->*w = 10.1f; //would throw an exception LabelNotFound
}
Related
What I want is
template<typename T>
bool larger(T a, T b){
if(a.x>b.x&&a.y>b.y&&a.z>b.z) return true;
return false;
}
.
But , each T may only have some of the x,y,z values, for example
struct Txy{
double x,y;
}
So for Txy I would only judge by x and y.
I know I could specify the template to do so, but with many Ts there's no difference between write a function for each T...
Is there any better way to deal with this problem?
You say you can not modify the structs... but are they raw structs or do they have constructors?
If they are raw structs, what you can do is to use the numero uno solution for everything in computers: add one extra layer of indirection.
Instead of having larger compare the Ts directly and have to overload or specialize each one, have a new larger2 that compares "completed" versions of the structs, where the completed version is computed more or less generically:
// Your original one, won't work
template<typename T>
bool larger(T a, T b){
if(a.x>b.x&&a.y>b.y&&a.z>b.z) return true;
return false;
}
struct Txy {
int x, y;
};
struct Txz {
int x, z;
};
struct Txyz {
int x, y, z;
};
// This struct "completizes" Ts, adding manually the missing members.
// Note for this to work, we are forced to add constructors to Completizer
// (unless I am missing something)
template <typename T>
struct Completizer: T {
// this constructor is needed
Completizer (T const& t): T(t) {}
};
template<> struct Completizer<Txy>: Txy {
int z;
Completizer (Txy const& t): Txy(t), z(0) {}
};
template<> struct Completizer<Txz>: Txz {
int y;
Completizer (Txz const& t): Txz(t), y(0) {}
};
// this is the thing that actually does the magic
template<typename T>
bool larger2_impl(Completizer<T> const& a, Completizer<T> const& b) {
if(a.x>b.x&&a.y>b.y&&a.z>b.z) return true;
return false;
}
// the trick here is to have our "larger" function not compare the Ts directly,
// but do so by means of Completizer<T>
template<typename T>
bool larger2(T const& a, T const& b) {
return larger2_impl(Completizer<T>(a), Completizer<T>(b));
}
int main () {
Txy xy1 = {1, 2}, xy2 = {2, 3};
Txz xz1 = {1, 2}, xz2 = {2, 3};
Txyz xyz1 = {1, 2, 3}, xyz2 = {2, 3, 4};
// only here to avoid warnings
(void)xy1; (void)xy2;
(void)xz1; (void)xz2;
//larger(xy1, xy2); // does not compile
larger2(xyz1, xyz2); // compiles
larger2(xy1, xy2); // compiles
larger2(xz1, xz2); // compiles
}
The advantage of this method is that it scales somewhat easier if you start adding more members, since you can derive Completizer<Txyzw> from Completizer<Txyz>.
#include <iostream>
template <typename T, typename = void> struct has_member_x : std::false_type{};
template <typename T> struct has_member_x<T, decltype((void)T::x, void())> : std::true_type {};
template <typename T, typename = void> struct has_member_y : std::false_type{};
template <typename T> struct has_member_y<T, decltype((void)T::y, void())> : std::true_type {};
template <typename T, typename = void> struct has_member_z : std::false_type{};
template <typename T> struct has_member_z<T, decltype((void)T::z, void())> : std::true_type {};
struct Txy {
int x, y;
};
struct Txz {
int x, z;
};
template<typename T>
bool larger(T a, T b){
if constexpr (has_member_x<T>::value)
if (a.x <= b.x)
return false;
if constexpr (has_member_y<T>::value)
if (a.y <= b.y)
return false;
if constexpr (has_member_z<T>::value)
if (a.z <= b.z)
return false;
return true;
}
int main() {
std:: cout << larger(Txy{1,2}, Txy{2,3}) << "\n";
std:: cout << larger(Txz{1,2}, Txz{2,3}) << "\n";
}
https://repl.it/repls/LastPoliteType#main.cpp
The point is to create uniform struct for vertex and for matrix in c++
Pseudocode:
vector<COUNT, T> {
union {
T data[COUNT];
struct { T x, y, z, w; } // if size is 1 -> only x, if 2 -> only x and y ... etc
struct { T u, v; } // same }
}
template<COUNT, VCOUNT, T>
matrix<COUNT, VCOUNT> : vector<COUNT, vector<VCOUNT, T>> {}
And use like this:
void foo() {
v3f pos(0,1,0);
pos.xy /= pos.z;
m4f transform = {{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,3.456}}
transform.x.y = transform[3][2];
}
I am too close to make this possible, the only thing I can't reach is initialization of matrix:
v4f v; // v4f typedef of vector<4,float>, m4f is matrix<4,4,float>
v = {0,0,1,0}; //work
m4f mat = {v,v,{},v}; //don't work "excess elements in struct initialization"
m4f mat = {{v,v,{}, v}}; // same
Some implementation details
namespace math::detail
{
template<int SIZE, typename T>
struct vector_base {
T _[SIZE];
};
template<typename T>
struct vector_base<1, T> {
union { T _[1]; T x; };
};
template<typename T>
struct vector_base<2, T> {
union { T _[2]; struct { T x, y; }; struct { T u, v; }; };
};
template<typename T>
struct vector_base<3, T> {
union { T _[3]; struct { T x, y, z; }; };
};
template<typename T>
struct vector_base<4, T> {
union { T _[4]; struct { T x, y, z, w; }; };
};
}
namespace math
{
enum VecI { X, Y, Z, W, U = X, V = Y };
template<int SIZE, typename T>
struct vector : detail::vector_base<SIZE, T>
{
using base = detail::vector_base<SIZE, T>;
vector() = default;
template<typename ... Types,
typename std::enable_if<sizeof...(Types) == SIZE, int>::type = 0>
constexpr vector(Types ... s) : base{static_cast<T>(s)... } {}
// some foos, operator overloading
}
template<int size, int vector_size, typename type>
struct matrix : vector<size, vector<vector_size, type>>
{
// special foos only for matrix
}
}
Vector constructor with parameter pack is not compiling in case of matrix:
error: no matching constructor for initialization of 'vector<3, vector<3, float> >'
return {{{1,0,0}, {0,1,0}, {0,0,1}}};
^~~~~~~~~~~~~~~~~~~~~~~~~~~
/math/vector/base.h:62:15: note: candidate template ignored: requirement 'sizeof...(Types) == 3' was not satisfied [with Types = <>]
constexpr vector(Types ... s) : base{static_cast<T>(s)... } {}
^
Next implementation of matrix at least compile, but need initializing with extra braces {{{},{},{}} and can't inherit some vector properties:
template<int size, int vector_size, typename type>
struct matrix
{
vector _[size];
// foos
}
I am also tried writing constructors for vector_base, for matrix, deduction guides for all of them, nothing works.
How to initialize array of arrays in C++ with automatic deduction (can initialize {5, 0.43, .1f} without errors of type incompatibilities), without implicitly writing types like in m4f mat = {v4f(0,0,1),v4f(0,1,0), v4f(1,0,0)}, and without "excess" nested braces {{{}}}?
My compiler is clang:9.0.0 with c++17, also can use c++2a, or c++20 when it comes.
Answer to your actual question
C++ is just lousy at deducing things from braced lists. It won't look at the lists, notice your constructor is initializing a member with them, and try to fit the braces to that member. Basically, multi-level brace initialization only works if it doesn't have to deduce anything (e.g. a non-template constructor, aggregate initialization, or a varible-length initializer_list)
To get this working for you, give each base type a simple constructor:
template<typename T>
struct vector_base<2, T> {
vector_base(T x, T y) : x(x), y(y) { } // <-- add these to each specialization
union { T _[2]; struct { T x, y; }; struct { T u, v; }; };
};
Then, for the generic derived type, promote the constructor:
template<int SIZE, typename T>
struct vector : detail::vector_base<SIZE, T>
{
using detail::vector_base<SIZE, T>::vector_base;
/* overloads and methods */
};
Now all forms of vector have a non-generic constructor and brace-initialization works as expected.
math::vector<3, math::vector<3, float>> foo {{1,0,0},{0,1,0},{0,0,1}};
Now, about the type punning
There's actually a neat trick for adding array-like access to struct members without breaking strict aliasing here: Overload the [] operator to access a static constexpr array of member-to-pointers to your struct members.
Then there is only one level of indirection between array-like access and member access, in a constant-foldable map. This allows the compiler to, in most cases, produce the same assembly as if you used union punning or a reinterpret_cast to access the member data in a for loop.
Applied to your code:
template <size_t SIZE, typename T>
struct vector_base;
template <typename T>
struct vector_base<1, T> {
constexpr vector_base(T x) : x(x) {}
T x;
constexpr T& operator [](size_t index) { return this->*(vector_base::map[index]); }
constexpr const T& operator [](size_t index) const { return this->*(vector_base::map[index]); }
static constexpr T vector_base::* map[1] = {&vector_base::x};
};
template <typename T>
struct vector_base<2, T> {
constexpr vector_base(T x, T y) : x(x), y(y) {}
union {
T x;
T u;
};
union {
T y;
T v;
};
constexpr T& operator [](size_t index) { return this->*(vector_base::map[index]); }
constexpr const T& operator [](size_t index) const { return this->*(vector_base::map[index]); }
static constexpr T vector_base::* map[2] = {&vector_base::x, &vector_base::y};
};
template <typename T>
struct vector_base<3, T> {
constexpr vector_base(T x, T y, T z) : x(x), y(y), z(z) {}
T x;
T y;
T z;
constexpr T& operator [](size_t index) { return this->*(vector_base::map[index]); }
constexpr const T& operator [](size_t index) const { return this->*(vector_base::map[index]); }
static constexpr T vector_base::* map[3] = {&vector_base::x, &vector_base::y, &vector_base::z};
};
template <typename T>
struct vector_base<4, T> {
constexpr vector_base(T x, T y, T z, T w) : x(x), y(y), z(z), w(w) {}
T x;
T y;
T z;
T w;
constexpr T& operator [](size_t index) { return this->*(vector_base::map[index]); }
constexpr const T& operator [](size_t index) const { return this->*(vector_base::map[index]); }
static constexpr T vector_base::* map[4] = {&vector_base::x, &vector_base::y, &vector_base::z, &vector_base::w};
};
Note: the union between x/y and u/v is actually well-formed, even for accessing inactive members
Demo: https://godbolt.org/z/ibpSqB
I'm new to SFINAE and I'm trying to write a simple Vector template. What I'm trying to achieve is to enable the z member variable based on the dimensions set for the Vector.
I have tried to achieve this effect using the following code:
template<unsigned int DIMENSIONS>
class Vector {
// The x and y variables (same as z)
template<typename = typename std::enable_if<DIMENSIONS >= 3>::type>
/// <summary>
/// The z coordinate.
/// </summary>
Float& z;
Vector() : x(values[0]), y(values[1]) {
}
// Other member functions and variables
std::vector<float> values;
};
template <>
Vector<3>::Vector() : x(values[0]), y(values[1]), z(values[2]) {
}
This should enable the z variable when there are 3 or more dimensions. This makes the compiler complain with the following error:
'Vector<DIMENSIONS>::z': only static data member templates are allowed
Also, I'm not entirely sure how to use initialization lists with SFINAE in such a situation. Because if the dimensions are smaller than 3, z would not have to be initialized (since it wouldn't exist). So far I've used a specialized constructor for 3D vectors (see the code above).
But intelliisense still reports that Members 'x', 'y', 'z' are not initialized in this constructor.
Any help would be appreciated.
You could do something like this:
struct no_z_var {};
struct z_var
{
float z;
};
template<std::size_t n>
struct my_vector : std::conditional_t<( n >= 3 ), z_var, no_z_var>
{
float x, y;
};
int main()
{
my_vector<3> mv3;
mv3.z = 3.4f;
my_vector<2> mv2;
mv2.z = 3.4f; // error; no 'z'
}
However, is this a good solution/design? I'm not so sure; it can get messy. It will most likely present other difficulties during the implementation...
You simply can't conditionally enable variables like that. Your best bet is just to provide multiple specializations of Vector:
template <>
struct Vector<2> {
float x, y;
Vector(std::vector<float> const& values)
: x(values[0])
, y(values[1])
{ }
};
template <>
struct Vector<3> {
float x, y, z;
Vector(std::vector<float> const& values)
: x(values[0])
, y(values[1])
, z(values[2])
{ }
};
// etc.
Though it might be more straightforward to use an array instead:
template <size_t DIM>
struct Vector {
std::array<float, DIM> values;
Vector(std::vector<float> const& vs)
: Vector(vs, std::make_index_sequence<DIM>{})
{ }
private:
template <size_t... Is>
Vector(std::vector<float> const& vs, std::index_sequence<Is...> )
: values{{vs[Is]...}}
{ }
};
You could use inheritance.
template<int n>
struct VecBase;
template<>
struct VecBase<1> {
float x;
};
template<>
struct VecBase<2> : VecBase<1> {
float y;
};
template<>
struct VecBase<3> : VecBase<2> {
float z;
};
template<>
struct VecBase<4> : VecBase<3> {
float w;
};
And then define your vector class:
template<int n>
struct Vector : VecBase<n> {
// operations
};
If you want to make n-dimentional operation, you can use std::get and std::index_sequence. Let's start by making overloads for std::get:
namespace std {
template<size_t I, int n, enable_if_t<(I == 0 && I < n), int> = 0>
float& get(VecBase<n>& vec) {
return vec.x;
}
template<size_t I, int n, enable_if_t<(I == 1 && I < n), int> = 0>
float& get(VecBase<n>& vec) {
return vec.y;
}
template<size_t I, int n, enable_if_t<(I == 2 && I < n), int> = 0>
float& get(VecBase<n>& vec) {
return vec.z;
}
template<size_t I, int n, enable_if_t<(I == 3 && I < n), int> = 0>
float& get(VecBase<n>& vec) {
return vec.w;
}
}
Note that you will have to implement them for const.
Then, you can implement your operations by making a base class that implement operations:
template<int, typename>
struct VecOps;
template<int n, std::size_t... S>
struct VecOps<n, std::index_sequence<S...>> : VecBase<n> {
float dotp(Vector<n>& vec) const {
// Where sum is adding each parameter variadically.
return sum((std::get<S>(*this) * std::get<S>(vec))...);
}
};
And finally, make Vector extending VecOps:
template<int n>
struct Vector : VecOps<n, std::make_index_sequence<n>> {};
Note that I don't have a compiler at my disposition for the moment. If your got compiler error, just leave a comment and I'll check this out.
Specialize the entrie class for 1, 2, 3 dimensions if you want this, i.e., template<> class Vector<3> { ... }.
You don't really need SFINAE here. Just use specialization:
template<unsigned int DIMENSIONS>
class Vector {
template<int I>
struct ZContainer {};
template<> struct ZContainer<3> {
float z;
};
ZContainer<DIMENSIONS> possibleZ;
};
This has the advantage that you don't need additional templates. You can just add functions to the ZContainers for your class behavior.
I would appreciate your help for the following simplified example:
template<int N>
struct Q {
struct X {
virtual int v() = 0;
};
template<int i>
struct Z : X {
virtual int v() { return i; }
};
Z<0> z1;
/* ... */
Z<N-1> zN;
X * x[N] = { &z1, /* ... */ &zN };
};
Q<4> q;
The ultimate goal of this example is to create N elements in q.x each pointing to an object instance created from the template with its own parameter .
#include <utility>
#include <tuple>
#include <iostream>
template <int N, typename T = std::make_index_sequence<N>>
struct Q;
template <int N, std::size_t... Is>
struct Q<N, std::index_sequence<Is...>>
{
struct X
{
virtual int v() = 0;
};
template <int i>
struct Z : X
{
virtual int v() { return i; }
};
std::tuple<Z<Is>...> z;
X * x[N] = { &std::get<Is>(z)... };
};
int main()
{
Q<4> q;
std::cout << q.x[0]->v() << std::endl;
std::cout << q.x[1]->v() << std::endl;
}
DEMO
You may use the following:
namespace detail
{
template <template <int> class Z, typename Seq> struct tuple_Z;
template <template <int> class Z, std::size_t ... Is>
struct tuple_Z<Z, std::index_sequence<Is...>>
{
using type = std::tuple<Z<Is>...>;
};
template <typename X, std::size_t N, typename Tuple, std::size_t ... Is>
constexpr std::array<X*, N> make_X_Array(Tuple& t, std::index_sequence<Is...>)
{
return {{(&std::get<Is>(t))...}};
}
}
template<int N>
struct Q {
struct X {
virtual int v() = 0;
};
template<int i>
struct Z : X {
virtual int v() { return i; }
};
Q() : Xs(detail::make_X_Array<X, N>(Zs, std::make_index_sequence<N>())) {}
typename detail::tuple_Z<Z, typename std::make_index_sequence<N>>::type Zs;
std::array<X*, N> Xs =
detail::make_X_Array<X, N>(Zs, std::make_index_sequence<N>());
};
Live example
I'm trying to implement a hypercubeclass, that is, multidimensional vectors.
I have a problem generalizing it. I'm able to make one for a three dimensional hypercube, but as mentioned, the problem is generalizing it. Could anyone help me? You should be able to write hypercube<4> w(5) to get 4 dimensions and 5 elements in each vector that is 5*5*5*5 elements in total.
Here is the code I have for the three dimensional version:
#include <vector>
using std::vector;
using namespace std;
template <int b>
class Hypercube {
public:
Hypercube(int a) : intvec(a){
for (int i = 0; i<a;i++) {
intvec[i].resize(a);
for (int j = 0;j<a;j++) {
intvec[i][j].resize(a);
}
}
}
vector<vector<int> >& operator[](int i) {
return intvec[i];
}
vector<vector<vector<int> > > intvec;
};
For this to work, you need recursive inheritence to provide the correct vector type and the initialization function. Both work recursively, for which I created a little helper struct called hcube_info:
// hypercube.h
#include <vector>
template<unsigned N>
struct hcube_info;
template<>
struct hcube_info<1>
{ // base version
typedef std::vector<int> type;
static type init(unsigned innerdim, int value = 0){
return type(innerdim, value);
}
};
template<unsigned N>
struct hcube_info
{ // recursive definition, N dimensions
private:
typedef hcube_info<N-1> base;
typedef typename base::type btype;
public:
typedef std::vector<btype> type;
static type init(unsigned innerdim, int value = 0){
return type(innerdim, base::init(innerdim, value));
}
};
As you can see, recursion all the way to the one dimensional base case. We also need to recursively initialize the vector to pass the inner dimension all the way through.
And now the real class, a nice interface around the hcube_info:
template<unsigned N>
struct hypercube
{
private:
typedef hcube_info<N> info;
typedef typename info::type vec_type;
public:
typedef typename vec_type::value_type value_type;
typedef typename vec_type::size_type size_type;
explicit hypercube(unsigned innerdim, unsigned value = 0)
: c(info::init(innerdim, value))
{
}
value_type& operator[](unsigned i){
return c[i];
}
size_type size() const{ return c.size(); }
private:
vec_type c;
};
Test program:
#include "hypercube.h"
#include <iostream>
int main(){
hypercube<4> c(5);
unsigned s = c.size() * // dim 1
c[0].size() * // dim 2
c[0][0].size() * // dim 3
c[0][0][0].size(); // dim 4
std::cout << s << '\n'; // outputs: 625 -> 5 * 5 * 5 * 5 -> 5^4
}
I would suggest something along those lines:
template <typename T, unsigned dim> class HQ {
std::vector<HQ<T,(dim-1)> > vector;
public:
HQ(unsigned size) : vector(size,HQ<T,(dim-1)>(size)) {}
};
template <typename T> class HQ<T,1> {
std::vector<T> vector;
public:
HQ(unsigned size) : vector(size,T()) {}
};
template <typename T> class HQ<T,0> {};
You can then implement your accessors for the first both templates as you wish. You can also make things a bit more simple and robust by allowing zero-dimensional matrices:
template <typename T, unsigned dim> class HQ {
std::vector<HQ<T,(dim-1)> > vector;
public:
HQ(unsigned size) : vector(size,HQ<T,(dim-1)>(size)) {}
};
template <typename T> class HQ<T,0> {
T data;
public:
HQ(unsigned size) : data() {}
};
I imagine an access operator would look something like this:
template <typename T, unsigned dim> HQ<T,(dim-1)>& HQ<T,dim>::operator[](unsigned i) {
return vector[i];
}
template <typename T, unsigned dim> HQ<T,(dim-1)> const& HQ<T,dim>::operator[](unsigned i) const {
return vector[i];
}
such that you can write
HQ<int,4> hq(5);
hq[1][4][2][0] = 77;